From 148b1f1708913490752de798ca640b427e471ea2 Mon Sep 17 00:00:00 2001 From: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Date: Wed, 15 Jan 2020 15:12:30 -0500 Subject: [PATCH 001/603] adding yieldmo vendor id to usersync (#1166) --- adapters/yieldmo/usersync.go | 2 +- adapters/yieldmo/usersync_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/yieldmo/usersync.go b/adapters/yieldmo/usersync.go index f853bbb86a5..041e7e8f073 100644 --- a/adapters/yieldmo/usersync.go +++ b/adapters/yieldmo/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewYieldmoSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldmo", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldmo", 173, temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldmo/usersync_test.go b/adapters/yieldmo/usersync_test.go index 5ae437c8f00..598710ec742 100644 --- a/adapters/yieldmo/usersync_test.go +++ b/adapters/yieldmo/usersync_test.go @@ -25,6 +25,6 @@ func TestYieldmoSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.yieldmo.com/pbsync?gdpr=0&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.EqualValues(t, 173, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } From 7d9b1ece872c8fb1a339ebba99f0878ac121de16 Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Thu, 16 Jan 2020 10:13:54 -0600 Subject: [PATCH 002/603] Add SmartRTB adapter (#1071) --- adapters/smartrtb/smartrtb.go | 189 ++++++++++++++++++ adapters/smartrtb/smartrtb_test.go | 11 + .../smartrtbtest/exemplary/banner.json | 134 +++++++++++++ .../smartrtbtest/exemplary/video.json | 134 +++++++++++++ .../smartrtbtest/params/race/banner.json | 5 + .../smartrtbtest/params/race/video.json | 5 + .../supplemental/bad-bidder-ext.json | 31 +++ .../supplemental/bad-imp-ext.json | 32 +++ .../supplemental/bad-pub-value-empty.json | 37 ++++ .../supplemental/bad-pub-value.json | 37 ++++ .../supplemental/bad-request.json | 70 +++++++ .../smartrtbtest/supplemental/empty-imps.json | 14 ++ .../supplemental/invalid-bid-ext.json | 93 +++++++++ .../supplemental/invalid-bid-format.json | 95 +++++++++ .../supplemental/invalid-bid-json.json | 76 +++++++ .../supplemental/invalid-imp-ext.json | 32 +++ .../smartrtbtest/supplemental/nobid.json | 69 +++++++ .../supplemental/non-http-ok.json | 76 +++++++ adapters/smartrtb/usersync.go | 12 ++ adapters/smartrtb/usersync_test.go | 20 ++ config/config.go | 2 + docs/bidders/smartrtb.md | 39 ++++ exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_smartrtb.go | 8 + static/bidder-info/smartrtb.yaml | 11 + static/bidder-params/smartrtb.json | 27 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 29 files changed, 1266 insertions(+) create mode 100644 adapters/smartrtb/smartrtb.go create mode 100644 adapters/smartrtb/smartrtb_test.go create mode 100644 adapters/smartrtb/smartrtbtest/exemplary/banner.json create mode 100644 adapters/smartrtb/smartrtbtest/exemplary/video.json create mode 100644 adapters/smartrtb/smartrtbtest/params/race/banner.json create mode 100644 adapters/smartrtb/smartrtbtest/params/race/video.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/bad-bidder-ext.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/bad-imp-ext.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value-empty.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/bad-request.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-ext.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-format.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/invalid-imp-ext.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/nobid.json create mode 100644 adapters/smartrtb/smartrtbtest/supplemental/non-http-ok.json create mode 100644 adapters/smartrtb/usersync.go create mode 100644 adapters/smartrtb/usersync_test.go create mode 100644 docs/bidders/smartrtb.md create mode 100644 openrtb_ext/imp_smartrtb.go create mode 100644 static/bidder-info/smartrtb.yaml create mode 100644 static/bidder-params/smartrtb.json diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go new file mode 100644 index 00000000000..5edaae6f289 --- /dev/null +++ b/adapters/smartrtb/smartrtb.go @@ -0,0 +1,189 @@ +package smartrtb + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/golang/glog" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// Base adapter structure. +type SmartRTBAdapter struct { + EndpointTemplate template.Template +} + +// Bid request extension appended to downstream request. +// PubID are non-empty iff request.{App,Site} or +// request.{App,Site}.Publisher are nil, respectively. +type bidRequestExt struct { + PubID string `json:"pub_id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ForceBid bool `json:"force_bid,omitempty"` +} + +// bidExt.CreativeType values. +const ( + creativeTypeBanner string = "BANNER" + creativeTypeVideo = "VIDEO" + creativeTypeNative = "NATIVE" + creativeTypeAudio = "AUDIO" +) + +// Bid response extension from downstream. +type bidExt struct { + CreativeType string `json:"format"` +} + +func NewSmartRTBBidder(endpointTemplate string) adapters.Bidder { + template, err := template.New("endpointTemplate").Parse(endpointTemplate) + if err != nil { + glog.Fatal("Template URL error") + return nil + } + return &SmartRTBAdapter{EndpointTemplate: *template} +} + +func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: pubID} + return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) +} + +func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error { + var ext adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return adapters.BadInput(err.Error()) + } + + var src openrtb_ext.ExtImpSmartRTB + if err := json.Unmarshal(ext.Bidder, &src); err != nil { + return adapters.BadInput(err.Error()) + } + + if dst.PubID == "" { + dst.PubID = src.PubID + } + + if src.ZoneID != "" { + imp.TagID = src.ZoneID + } + return nil +} + +func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var imps []openrtb.Imp + var err error + ext := bidRequestExt{} + nrImps := len(brq.Imp) + errs := make([]error, 0, nrImps) + + for i := 0; i < nrImps; i++ { + imp := brq.Imp[i] + if imp.Banner == nil && imp.Video == nil { + continue + } + + err = parseExtImp(&ext, &imp) + if err != nil { + errs = append(errs, err) + continue + } + + imps = append(imps, imp) + } + + if len(imps) == 0 { + return nil, errs + } + + if ext.PubID == "" { + return nil, append(errs, adapters.BadInput("Cannot infer publisher ID from bid ext")) + } + + brq.Ext, err = json.Marshal(ext) + if err != nil { + return nil, append(errs, err) + } + + brq.Imp = imps + + rq, err := json.Marshal(brq) + if err != nil { + return nil, append(errs, err) + } + + url, err := s.buildEndpointURL(ext.PubID) + if err != nil { + return nil, append(errs, err) + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + return []*adapters.RequestData{{ + Method: "POST", + Uri: url, + Body: rq, + Headers: headers, + }}, errs +} + +func (s *SmartRTBAdapter) MakeBids( + brq *openrtb.BidRequest, drq *adapters.RequestData, + rs *adapters.ResponseData, +) (*adapters.BidderResponse, []error) { + if rs.StatusCode == http.StatusNoContent { + return nil, nil + } else if rs.StatusCode == http.StatusBadRequest { + return nil, []error{adapters.BadInput("Invalid request.")} + } else if rs.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected HTTP status %d.", rs.StatusCode), + }} + } + + var brs openrtb.BidResponse + if err := json.Unmarshal(rs.Body, &brs); err != nil { + return nil, []error{err} + } + + rv := adapters.NewBidderResponseWithBidsCapacity(5) + for _, seat := range brs.SeatBid { + for i := range seat.Bid { + var ext bidExt + if err := json.Unmarshal(seat.Bid[i].Ext, &ext); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Invalid bid extension from endpoint.", + }} + } + + var btype openrtb_ext.BidType + switch ext.CreativeType { + case creativeTypeBanner: + btype = openrtb_ext.BidTypeBanner + case creativeTypeVideo: + btype = openrtb_ext.BidTypeVideo + default: + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported creative type %s.", + ext.CreativeType), + }} + } + + seat.Bid[i].Ext = nil + + rv.Bids = append(rv.Bids, &adapters.TypedBid{ + Bid: &seat.Bid[i], + BidType: btype, + }) + } + } + return rv, nil +} diff --git a/adapters/smartrtb/smartrtb_test.go b/adapters/smartrtb/smartrtb_test.go new file mode 100644 index 00000000000..3f76ed044a8 --- /dev/null +++ b/adapters/smartrtb/smartrtb_test.go @@ -0,0 +1,11 @@ +package smartrtb + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "smartrtbtest", NewSmartRTBBidder("http://market-east.smrtb.com/json/publisher/rtb?pubid=test")) +} diff --git a/adapters/smartrtb/smartrtbtest/exemplary/banner.json b/adapters/smartrtb/smartrtbtest/exemplary/banner.json new file mode 100644 index 00000000000..436f6298a16 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/exemplary/banner.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXK", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 0.01, + "ext": { + "format": "BANNER" + } + } + ] + }, + { + "bid": [ + { + "adm": "", + "crid": "test_video_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "2", + "price": 0.01, + "ext": { + "format": "VIDEO" + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 0.01, + "id": "1" + }, + "type": "banner" + }, + { + "bid": { + "adm": "", + "crid": "test_video_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 0.01, + "id": "2" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/exemplary/video.json b/adapters/smartrtb/smartrtbtest/exemplary/video.json new file mode 100644 index 00000000000..436f6298a16 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/exemplary/video.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXK", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 0.01, + "ext": { + "format": "BANNER" + } + } + ] + }, + { + "bid": [ + { + "adm": "", + "crid": "test_video_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "2", + "price": 0.01, + "ext": { + "format": "VIDEO" + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 0.01, + "id": "1" + }, + "type": "banner" + }, + { + "bid": { + "adm": "", + "crid": "test_video_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 0.01, + "id": "2" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/params/race/banner.json b/adapters/smartrtb/smartrtbtest/params/race/banner.json new file mode 100644 index 00000000000..207a504539f --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true +} \ No newline at end of file diff --git a/adapters/smartrtb/smartrtbtest/params/race/video.json b/adapters/smartrtb/smartrtbtest/params/race/video.json new file mode 100644 index 00000000000..207a504539f --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true +} \ No newline at end of file diff --git a/adapters/smartrtb/smartrtbtest/supplemental/bad-bidder-ext.json b/adapters/smartrtb/smartrtbtest/supplemental/bad-bidder-ext.json new file mode 100644 index 00000000000..b261415de4d --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/bad-bidder-ext.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": null + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/bad-imp-ext.json b/adapters/smartrtb/smartrtbtest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..1c0f57d2f34 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/bad-imp-ext.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "publisher": { }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": null + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value-empty.json b/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value-empty.json new file mode 100644 index 00000000000..aca21036b24 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value-empty.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "publisher": { }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Cannot infer publisher ID from bid ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value.json b/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value.json new file mode 100644 index 00000000000..93b45c747fd --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/bad-pub-value.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": 0, + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field ExtImpSmartRTB.pub_id of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/bad-request.json b/adapters/smartrtb/smartrtbtest/supplemental/bad-request.json new file mode 100644 index 00000000000..cf03832ddff --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/bad-request.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "abc", + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "fake", + "force_bid": false + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "imp": [{ + "id": "imp123", + "tagid": "fake", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "fake", + "force_bid": false + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Invalid request.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json b/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json new file mode 100644 index 00000000000..a92add70825 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json @@ -0,0 +1,14 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "publisher": { }, + "imp": [ + { + "id": "imp123" + } + ] + } +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-ext.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-ext.json new file mode 100644 index 00000000000..49527e1ecd4 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-ext.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXK", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 0.01, + "ext": "notvalidjsonhaha" + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid bid extension from endpoint.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-format.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-format.json new file mode 100644 index 00000000000..2f6bc07edb8 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-format.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXK", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "hi", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 0.01, + "ext": { + "format": "ALIEN_FORMAT" + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported creative type ALIEN_FORMAT.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json new file mode 100644 index 00000000000..c56e7f21515 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXK", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": "imnotyourfather" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-imp-ext.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..13485f797ba --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-imp-ext.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "publisher": { }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": "notjsontho" + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/nobid.json b/adapters/smartrtb/smartrtbtest/supplemental/nobid.json new file mode 100644 index 00000000000..2733d8dba96 --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/nobid.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXKz", + "force_bid": false + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXKz", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXKz", + "force_bid": false + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/smartrtb/smartrtbtest/supplemental/non-http-ok.json b/adapters/smartrtb/smartrtbtest/supplemental/non-http-ok.json new file mode 100644 index 00000000000..3acafadc62f --- /dev/null +++ b/adapters/smartrtb/smartrtbtest/supplemental/non-http-ok.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp123", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXKz", + "force_bid": false + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test", + "body":{ + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp123", + "tagid": "N4zTDq3PPEHBIODv7cXKz", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXKz", + "force_bid": false + } + } + }], + "ext": { + "pub_id": "test" + } + } + }, + "mockResponse": { + "status": 500 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected HTTP status 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartrtb/usersync.go b/adapters/smartrtb/usersync.go new file mode 100644 index 00000000000..2f7b1dc3339 --- /dev/null +++ b/adapters/smartrtb/usersync.go @@ -0,0 +1,12 @@ +package smartrtb + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmartRTBSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smartrtb", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smartrtb/usersync_test.go b/adapters/smartrtb/usersync_test.go new file mode 100644 index 00000000000..ae3ae5dc007 --- /dev/null +++ b/adapters/smartrtb/usersync_test.go @@ -0,0 +1,20 @@ +package smartrtb + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestSmartRTBSyncer(t *testing.T) { + temp := template.Must(template.New("sync-template").Parse("http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")) + syncer := NewSmartRTBSyncer(temp) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + assert.NoError(t, err) + assert.Equal(t, "http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr=&gdpr_consent=&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 722aae1395c..8dc0bb14526 100644 --- a/config/config.go +++ b/config/config.go @@ -518,6 +518,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderRTBHouse doesn't have a good default. // openrtb_ext.BidderRubicon doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -700,6 +701,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1") + v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") diff --git a/docs/bidders/smartrtb.md b/docs/bidders/smartrtb.md new file mode 100644 index 00000000000..ffa88f663e8 --- /dev/null +++ b/docs/bidders/smartrtb.md @@ -0,0 +1,39 @@ +# SmartRTB Bidder + +[SmartRTB](https://smrtb.com/) supports the following parameters to be present in the `ext` object of impression requests: + +- "pub_id" type string - Required. Publisher ID assigned to you. +- "zone_id" type string - Optional. Enables mapping for further settings and reporting in the Marketplace UI. +- "force_bid" type bool - Optional. If zone ID is mapped, this may be set to always return fake sample bids (banner, video) + +Please contact us to create a new Smart RTB Marketplace account, and for any assistance in configuration. +You may email info@smrtb.com for inquiries. + +## Test Request + +This sample request is our global test placement and should always return a branded banner bid. + +``` + { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "test", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "smartrtb": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }] + } +``` diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 30a31727d6b..5795ad2c197 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -44,6 +44,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -108,6 +109,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), openrtb_ext.BidderSharethrough: sharethrough.NewSharethroughBidder(cfg.Adapters[string(openrtb_ext.BidderSharethrough)].Endpoint), + openrtb_ext.BidderSmartRTB: smartrtb.NewSmartRTBBidder(cfg.Adapters[string(openrtb_ext.BidderSmartRTB)].Endpoint), openrtb_ext.BidderSomoaudience: somoaudience.NewSomoaudienceBidder(cfg.Adapters[string(openrtb_ext.BidderSomoaudience)].Endpoint), openrtb_ext.BidderSonobi: sonobi.NewSonobiBidder(client, cfg.Adapters[string(openrtb_ext.BidderSonobi)].Endpoint), openrtb_ext.BidderSovrn: sovrn.NewSovrnBidder(client, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 9621b23dc81..f02a16b4350 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -57,6 +57,7 @@ const ( BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" + BidderSmartRTB BidderName = "smartrtb" BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" @@ -110,6 +111,7 @@ var BidderMap = map[string]BidderName{ "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, "sharethrough": BidderSharethrough, + "smartrtb": BidderSmartRTB, "somoaudience": BidderSomoaudience, "sonobi": BidderSonobi, "sovrn": BidderSovrn, diff --git a/openrtb_ext/imp_smartrtb.go b/openrtb_ext/imp_smartrtb.go new file mode 100644 index 00000000000..d056046bf9d --- /dev/null +++ b/openrtb_ext/imp_smartrtb.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtImpSmartRTB struct { + PubID string `json:"pub_id,omitempty"` + MedID string `json:"med_id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ForceBid bool `json:"force_bid,omitempty"` +} diff --git a/static/bidder-info/smartrtb.yaml b/static/bidder-info/smartrtb.yaml new file mode 100644 index 00000000000..c26184f91b7 --- /dev/null +++ b/static/bidder-info/smartrtb.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "engineering@smrtb.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/smartrtb.json b/static/bidder-params/smartrtb.json new file mode 100644 index 00000000000..3bbaab10736 --- /dev/null +++ b/static/bidder-params/smartrtb.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SmartRTB Adapter Params", + "description": "Required parameters for the SmartRTB server adapter", + "type": "object", + "properties": { + "pub_id": { + "type": "string", + "description": "Assigned publisher ID", + "minLength": 4 + }, + "med_id": { + "type": "string", + "description": "Property ID not zone ID not provided" + }, + "zone_id": { + "type": "string", + "description": "Specific zone ID for this placement, belonging to app/site", + "minLength": 20 + }, + "force_bid": { + "type": "boolean", + "description": "Force bids with a test creative" + } + }, + "required": [ "pub_id" ] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 6277993238a..2bfa5514063 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -40,6 +40,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -99,6 +100,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSomoaudience, somoaudience.NewSomoaudienceSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSonobi, sonobi.NewSonobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index dd2b5b5d1e9..3fb1d4d7fa4 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -51,6 +51,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, + string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, From bd43afbca80114e76d1b14a2843b71f8a5f62894 Mon Sep 17 00:00:00 2001 From: CPMStar Date: Thu, 16 Jan 2020 08:14:14 -0800 Subject: [PATCH 003/603] Added new adapter for CPMStar ad network banners and video (#1159) --- adapters/cpmstar/cpmstar.go | 161 ++++++++++++++++++ adapters/cpmstar/cpmstar_test.go | 11 ++ .../exemplary/banner-and-video.json | 154 +++++++++++++++++ .../cpmstar/cpmstartest/exemplary/banner.json | 100 +++++++++++ .../cpmstar/cpmstartest/exemplary/video.json | 55 ++++++ .../cpmstartest/supplemental/audio.json | 25 +++ .../supplemental/explicit-dimensions.json | 58 +++++++ .../invalid-response-no-bids.json | 50 ++++++ .../invalid-response-unmarshall-error.json | 66 +++++++ .../cpmstartest/supplemental/native.json | 25 +++ .../supplemental/no-imps-in-request.json | 18 ++ .../supplemental/server-error-code.json | 53 ++++++ .../supplemental/server-no-content.json | 45 +++++ .../supplemental/wrong-impression-ext.json | 26 +++ .../wrong-impression-mapping.json | 77 +++++++++ adapters/cpmstar/params_test.go | 54 ++++++ adapters/cpmstar/usersync.go | 13 ++ adapters/cpmstar/usersync_test.go | 25 +++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_cpmstar.go | 6 + static/bidder-info/cpmstar.yaml | 11 ++ static/bidder-params/cpmstar.json | 19 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 1061 insertions(+) create mode 100644 adapters/cpmstar/cpmstar.go create mode 100644 adapters/cpmstar/cpmstar_test.go create mode 100644 adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json create mode 100644 adapters/cpmstar/cpmstartest/exemplary/banner.json create mode 100644 adapters/cpmstar/cpmstartest/exemplary/video.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/audio.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/native.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/server-error-code.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/server-no-content.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json create mode 100644 adapters/cpmstar/params_test.go create mode 100644 adapters/cpmstar/usersync.go create mode 100644 adapters/cpmstar/usersync_test.go create mode 100644 openrtb_ext/imp_cpmstar.go create mode 100644 static/bidder-info/cpmstar.yaml create mode 100644 static/bidder-params/cpmstar.json diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go new file mode 100644 index 00000000000..ef6abe70cb7 --- /dev/null +++ b/adapters/cpmstar/cpmstar.go @@ -0,0 +1,161 @@ +package cpmstar + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type Adapter struct { + endpoint string +} + +func (a *Adapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var adapterRequests []*adapters.RequestData + + if err := preprocess(request); err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterReq, err := a.makeRequest(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterRequests = append(adapterRequests, adapterReq) + + return adapterRequests, errs +} + +func (a *Adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { + var err error + + jsonBody, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: jsonBody, + Headers: headers, + }, nil +} + +func preprocess(request *openrtb.BidRequest) error { + if len(request.Imp) == 0 { + return &errortypes.BadInput{ + Message: "No Imps in Bid Request", + } + } + for i := 0; i < len(request.Imp); i++ { + var imp = &request.Imp[i] + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + if err := validateImp(imp); err != nil { + return err + } + + var extImp openrtb_ext.ExtImpCpmstar + if err := json.Unmarshal(bidderExt.Bidder, &extImp); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + imp.Ext = bidderExt.Bidder + } + + return nil +} + +func validateImp(imp *openrtb.Imp) error { + if imp.Banner == nil && imp.Video == nil { + return &errortypes.BadInput{ + Message: "Only Banner and Video bid-types are supported at this time", + } + } + return nil +} + +// MakeBids based on cpmstar server response +func (a *Adapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected HTTP status code: %d. Run with request.debug = 1 for more info", responseData.StatusCode), + }} + } + + var bidResponse openrtb.BidResponse + + if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + if len(bidResponse.SeatBid) == 0 { + return nil, nil + } + + rv := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid)) + var errors []error + + for _, seatbid := range bidResponse.SeatBid { + for _, bid := range seatbid.Bid { + foundMatchingBid := false + bidType := openrtb_ext.BidTypeBanner + for _, imp := range bidRequest.Imp { + if imp.ID == bid.ImpID { + foundMatchingBid = true + if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + } + break + } + } + + if foundMatchingBid { + rv.Bids = append(rv.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } else { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid id='%s' could not find valid impid='%s'", bid.ID, bid.ImpID), + }) + } + } + } + return rv, errors +} + +func NewCpmstarBidder(endpoint string) *Adapter { + return &Adapter{ + endpoint: endpoint, + } +} diff --git a/adapters/cpmstar/cpmstar_test.go b/adapters/cpmstar/cpmstar_test.go new file mode 100644 index 00000000000..0a7f43f5ee7 --- /dev/null +++ b/adapters/cpmstar/cpmstar_test.go @@ -0,0 +1,11 @@ +package cpmstar + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "cpmstartest", NewCpmstarBidder("//host")) +} diff --git a/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json b/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..d4dcb4b8677 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + }, + { + "id": "test-video-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 654 + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + }, + { + "id": "test-video-imp-id", + "video": { + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640 + }, + "ext": { + "placementId": 154, + "subpoolId": 654 + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "cpmstar", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-video-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "banner" + }, + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29484110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29484110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/exemplary/banner.json b/adapters/cpmstar/cpmstartest/exemplary/banner.json new file mode 100644 index 00000000000..0bbe3060a63 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/banner.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "cpmstar", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/exemplary/video.json b/adapters/cpmstar/cpmstartest/exemplary/video.json new file mode 100644 index 00000000000..a0213cbdac1 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/video.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/audio.json b/adapters/cpmstar/cpmstartest/supplemental/audio.json new file mode 100644 index 00000000000..18ff171c7f5 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/audio.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-audio-request", + "imp": [ + { + "id": "unsupported-audio-imp", + "audio": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Only Banner and Video bid-types are supported at this time", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json b/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json new file mode 100644 index 00000000000..b8aad87514d --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json new file mode 100644 index 00000000000..a3cb9114caa --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + ], + "cur": "USD" + } + } + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json new file mode 100644 index 00000000000..e20acefe2c3 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + { + "bid": [ + { + "id": "uuid", + "impid": "some_test_ad", + "w": "728", + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/native.json b/adapters/cpmstar/cpmstartest/supplemental/native.json new file mode 100644 index 00000000000..a02db78db0f --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/native.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "native": { + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Only Banner and Video bid-types are supported at this time", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json b/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json new file mode 100644 index 00000000000..274a34227cf --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "No Imps in Bid Request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json b/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json new file mode 100644 index 00000000000..21e697d13f1 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected HTTP status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } diff --git a/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json b/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json new file mode 100644 index 00000000000..e56db95f8e8 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] + } diff --git a/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json new file mode 100644 index 00000000000..1e8de0acc66 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "rqid", + "imp": [ + { + "id": "impid", + "video": { + "w": 100, + "h": 200 + }, + "ext": { + "bidder": { + "placementId": "BOGUS" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field ExtImpCpmstar.placementId of type int", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..6ab02db0ca7 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "poolid": 154, + "subpoolid": 123 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "poolid": 154, + "subpoolid": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "BOGUS-IMPID", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid id='test-bid-id' could not find valid impid='BOGUS-IMPID'", + "comparison": "regex" + } +] +} \ No newline at end of file diff --git a/adapters/cpmstar/params_test.go b/adapters/cpmstar/params_test.go new file mode 100644 index 00000000000..cee471a8322 --- /dev/null +++ b/adapters/cpmstar/params_test.go @@ -0,0 +1,54 @@ +package cpmstar + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/cpmstar.json +// These also validate the format of the external API: request.imp[i].ext.cpmstar +// TestValidParams makes sure that the Cpmstar schema accepts all imp.ext fields which we intend to support. + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderCpmstar, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Cpmstar params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Cpmstar schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderCpmstar, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": 154}`, + `{"placementId": 154, "subpoolId": 123}`, +} + +var invalidParams = []string{ + `{}`, + `null`, + `true`, + `154`, + `{"placementId": "154"}`, // placementId should be numeric + `{"placementId": 154, "subpoolId": "123"}`, // placementId and subpoolId should both be numeric + `{"invalid_param": 123}`, +} diff --git a/adapters/cpmstar/usersync.go b/adapters/cpmstar/usersync.go new file mode 100644 index 00000000000..9c864e24ce3 --- /dev/null +++ b/adapters/cpmstar/usersync.go @@ -0,0 +1,13 @@ +package cpmstar + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +//NewCpmstarSyncer : +func NewCpmstarSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("cpmstar", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/cpmstar/usersync_test.go b/adapters/cpmstar/usersync_test.go new file mode 100644 index 00000000000..dae55e6302e --- /dev/null +++ b/adapters/cpmstar/usersync_test.go @@ -0,0 +1,25 @@ +package cpmstar + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestCpmstarSyncer(t *testing.T) { + syncURL := "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewCpmstarSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://server.cpmstar.com/usersync.aspx?gdpr=&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D%26gdpr_consent%3D%26us_privacy%3D%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.False(t, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 8dc0bb14526..d9b6ee6e55d 100644 --- a/config/config.go +++ b/config/config.go @@ -496,6 +496,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") @@ -678,6 +679,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24") + v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 5795ad2c197..95f5b7f5882 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -22,6 +22,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" + "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" @@ -79,6 +80,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(), openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), + openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f02a16b4350..7a3f24eb07f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -33,6 +33,7 @@ const ( BidderBrightroll BidderName = "brightroll" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" @@ -87,6 +88,7 @@ var BidderMap = map[string]BidderName{ "brightroll": BidderBrightroll, "consumable": BidderConsumable, "conversant": BidderConversant, + "cpmstar": BidderCpmstar, "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, diff --git a/openrtb_ext/imp_cpmstar.go b/openrtb_ext/imp_cpmstar.go new file mode 100644 index 00000000000..0b74f4d437d --- /dev/null +++ b/openrtb_ext/imp_cpmstar.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpCpmstar struct { + PoolId int `json:"placementId"` + SubPoolId int `json:"subpoolId,omitempty"` +} diff --git a/static/bidder-info/cpmstar.yaml b/static/bidder-info/cpmstar.yaml new file mode 100644 index 00000000000..097dfddd5b0 --- /dev/null +++ b/static/bidder-info/cpmstar.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "prebid@cpmstar.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/cpmstar.json b/static/bidder-params/cpmstar.json new file mode 100644 index 00000000000..576b503e793 --- /dev/null +++ b/static/bidder-params/cpmstar.json @@ -0,0 +1,19 @@ + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Cpmstar Adapter Params", + "description": "Schema to validate params accepted by the Cpmstar adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "Cpmstar-specific ID for ad pool" + }, + "subpoolId": { + "type": "integer", + "description": "Cpmstar-specific ID for ad subpool" + } + }, + "required": ["placementId"] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 2bfa5514063..5447cd28800 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" + "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" @@ -75,6 +76,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 3fb1d4d7fa4..ded8fd2bd78 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBrightroll): syncConfig, string(openrtb_ext.BidderConsumable): syncConfig, string(openrtb_ext.BidderConversant): syncConfig, + string(openrtb_ext.BidderCpmstar): syncConfig, string(openrtb_ext.BidderDatablocks): syncConfig, string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig, From 85022d14f7d93d58b3b4e64434dec15b629a66fa Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Wed, 22 Jan 2020 21:32:02 -0800 Subject: [PATCH 004/603] Update the Conversant sync pixel (#1161) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index d9b6ee6e55d..079d5b1f4c7 100644 --- a/config/config.go +++ b/config/config.go @@ -495,7 +495,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match/bounce/current?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26networkId%3D72582%26version%3D1%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") From 31c2204793cf0dc6c28ba9a49a861c7b0da4e54c Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Thu, 23 Jan 2020 17:04:18 +0200 Subject: [PATCH 005/603] Add imp.ext.is_rewarded_inventory flag for rewarded video in Rubicon (#1170) --- adapters/rubicon/rubicon.go | 9 ++++++- adapters/rubicon/rubicon_test.go | 42 ++++++++++++++++++++++++++++++++ openrtb_ext/imp.go | 3 +++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index e7461c48f7e..46caf262108 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -129,6 +129,7 @@ type rubiconVideoParams struct { type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` + VideoType string `json:"videotype,omitempty"` RP rubiconVideoExtRP `json:"rp"` } @@ -693,8 +694,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap continue } + // if imp.ext.is_rewarded_inventory = 1, set imp.video.ext.videotype = "rewarded" + var videoType = "" + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { + videoType = "rewarded" + } + videoCopy := *thisImp.Video - videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: rubiconExt.Video.VideoSizeID}} + videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, VideoType: videoType, RP: rubiconVideoExtRP{SizeID: rubiconExt.Video.VideoSizeID}} videoCopy.Ext, err = json.Marshal(&videoExt) thisImp.Video = &videoCopy thisImp.Banner = nil diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index dd9cea62bc7..d386daed5b1 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1270,6 +1270,48 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t assert.NotNil(t, rubiconReq.Imp[0].Video, "Video object must be in request impression") } +func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) { + bidder := new(RubiconAdapter) + + request := &openrtb.BidRequest{ + ID: "test-request-id", + Imp: []openrtb.Imp{{ + ID: "test-imp-id", + Video: &openrtb.Video{ + W: 640, + H: 360, + MIMEs: []string{"video/mp4"}, + Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, + MaxDuration: 30, + Linearity: 1, + API: []openrtb.APIFramework{}, + }, + Ext: json.RawMessage(`{ + "prebid":{ + "is_rewarded_inventory": 1 + }, + "bidder": { + "video": {"size_id": 1} + }}`), + }}, + } + + reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) + + rubiconReq := &openrtb.BidRequest{} + if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { + t.Fatalf("Unexpected error while decoding request: %s", err) + } + + videoExt := &rubiconVideoExt{} + if err := json.Unmarshal(rubiconReq.Imp[0].Video.Ext, &videoExt); err != nil { + t.Fatal("Error unmarshalling request.imp[i].video.ext object.") + } + + assert.Equal(t, "rewarded", videoExt.VideoType, + "Unexpected VideoType. Got %s. Expected %s", videoExt.VideoType, "rewarded") +} + func TestOpenRTBEmptyResponse(t *testing.T) { httpResp := &adapters.ResponseData{ StatusCode: http.StatusNoContent, diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go index 499d1f631bf..0e8f224b884 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -20,6 +20,9 @@ type ExtImp struct { type ExtImpPrebid struct { StoredRequest *ExtStoredRequest `json:"storedrequest"` + // Rewarded inventory signal, can be 0 or 1 + IsRewardedInventory int8 `json:"is_rewarded_inventory"` + // NOTE: This is not part of the official API, we are not expecting clients // migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} // at this time From 95200afb3cdcb054f120be21014ae3ff67221cfd Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 23 Jan 2020 16:12:48 +0100 Subject: [PATCH 006/603] [currencies] fix GetInfo() null ref issue (#1169) This CL fixes the null ref on `RateConverter.GetInfo()` when rates are nil. Issue: #1136 --- currencies/rate_converter.go | 6 +++++- currencies/rate_converter_test.go | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/currencies/rate_converter.go b/currencies/rate_converter.go index 63f09bd3c2e..6c6ed172652 100644 --- a/currencies/rate_converter.go +++ b/currencies/rate_converter.go @@ -172,11 +172,15 @@ func (rc *RateConverter) Rates() Conversions { // GetInfo returns setup information about the converter func (rc *RateConverter) GetInfo() ConverterInfo { + var rates *map[string]map[string]float64 + if rc.Rates() != nil { + rates = rc.Rates().GetRates() + } return converterInfo{ source: rc.syncSourceURL, fetchingInterval: rc.fetchingInterval, lastUpdated: rc.LastUpdated(), - rates: rc.Rates().GetRates(), + rates: rates, } } diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go index f9a14895347..cb5e2a0be54 100644 --- a/currencies/rate_converter_test.go +++ b/currencies/rate_converter_test.go @@ -66,6 +66,7 @@ func TestFetch_Success(t *testing.T) { rates := currencyConverter.Rates() assert.NotNil(t, rates, "Rates() should not return nil") assert.Equal(t, expectedRates, rates, "Rates() doesn't return expected rates") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestFetch_Fail404(t *testing.T) { @@ -92,6 +93,7 @@ func TestFetch_Fail404(t *testing.T) { assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestFetch_FailErrorHttpClient(t *testing.T) { @@ -118,6 +120,7 @@ func TestFetch_FailErrorHttpClient(t *testing.T) { assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestFetch_FailBadSyncURL(t *testing.T) { @@ -134,6 +137,7 @@ func TestFetch_FailBadSyncURL(t *testing.T) { // Verify: assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestFetch_FailBadJSON(t *testing.T) { @@ -174,6 +178,7 @@ func TestFetch_FailBadJSON(t *testing.T) { assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestFetch_InvalidRemoteResponseContent(t *testing.T) { @@ -201,6 +206,7 @@ func TestFetch_InvalidRemoteResponseContent(t *testing.T) { assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestInit(t *testing.T) { @@ -264,6 +270,7 @@ func TestInit(t *testing.T) { assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated should be set") rates := currencyConverter.Rates() assert.Equal(t, expectedRates, rates, "Conversions.Rates weren't the expected ones") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") if ticksCount == expectedTicks { currencyConverter.StopPeriodicFetching() @@ -361,6 +368,7 @@ func TestInitWithZeroDuration(t *testing.T) { assert.Equal(t, (time.Time{}), currencyConverter.LastUpdated(), "LastUpdated() shouldn't be set") _, ok := currencyConverter.Rates().(*currencies.ConstantRates) assert.True(t, ok, "Rates should be type of `currencies.ConstantRates`") + assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } func TestRates(t *testing.T) { From 84e2c26cd5df586e2422363ac5398235849d81fe Mon Sep 17 00:00:00 2001 From: Kevin Kerr Date: Fri, 24 Jan 2020 08:37:50 -0500 Subject: [PATCH 007/603] Fix triplelift User Sync (#1173) --- config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 079d5b1f4c7..282ae9dc2b5 100644 --- a/config/config.go +++ b/config/config.go @@ -525,8 +525,8 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") // openrtb_ext.BidderTappx doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gpdr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/sync?gpdr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. From 94cc35467aec9ea6d1abb4943532af0c3a92f6ca Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 27 Jan 2020 14:03:34 -0500 Subject: [PATCH 008/603] Enhance Message For Cache Errors (#1175) --- prebid_cache_client/client.go | 34 ++++++------- prebid_cache_client/client_test.go | 80 ++++++++++++++++++++++-------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 6da69f68243..58e2734ed25 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -92,15 +93,13 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s postBody, err := encodeValues(values) if err != nil { - glog.Errorf("Error creating JSON for prebid cache: %v", err) - errs = append(errs, fmt.Errorf("Error creating JSON for prebid cache: %v", err)) + logError(&errs, "Error creating JSON for prebid cache: %v", err) return uuidsToReturn, errs } httpReq, err := http.NewRequest("POST", c.putUrl, bytes.NewReader(postBody)) if err != nil { - glog.Errorf("Error creating POST request to prebid cache: %v", err) - errs = append(errs, fmt.Errorf("Error creating POST request to prebid cache: %v", err)) + logError(&errs, "Error creating POST request to prebid cache: %v", err) return uuidsToReturn, errs } @@ -112,9 +111,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s elapsedTime := time.Since(startTime) if err != nil { c.metrics.RecordPrebidCacheRequestTime(false, elapsedTime) - friendlyErr := fmt.Errorf("Error sending the request to Prebid Cache: %v; Duration=%v", err, elapsedTime) - glog.Error(friendlyErr) - errs = append(errs, friendlyErr) + logError(&errs, "Error sending the request to Prebid Cache: %v; Duration=%v, Items=%v, Payload Size=%v", err, elapsedTime, len(values), len(postBody)) return uuidsToReturn, errs } defer anResp.Body.Close() @@ -122,23 +119,19 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { - glog.Errorf("Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) - errs = append(errs, fmt.Errorf("Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody)) + logError(&errs, "Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) return uuidsToReturn, errs } currentIndex := 0 processResponse := func(uuidObj []byte, _ jsonparser.ValueType, _ int, err error) { if uuid, valueType, _, err := jsonparser.Get(uuidObj, "uuid"); err != nil { - glog.Errorf("Prebid Cache returned a bad value at index %d. Error was: %v. Response body was: %s", currentIndex, err, string(responseBody)) - errs = append(errs, fmt.Errorf("Prebid Cache returned a bad value at index %d. Error was: %v. Response body was: %s", currentIndex, err, string(responseBody))) + logError(&errs, "Prebid Cache returned a bad value at index %d. Error was: %v. Response body was: %s", currentIndex, err, string(responseBody)) } else if valueType != jsonparser.String { - glog.Errorf("Prebid Cache returned a %v at index %d in: %v", valueType, currentIndex, string(responseBody)) - errs = append(errs, fmt.Errorf("Prebid Cache returned a %v at index %d in: %v", valueType, currentIndex, string(responseBody))) + logError(&errs, "Prebid Cache returned a %v at index %d in: %v", valueType, currentIndex, string(responseBody)) } else { if uuidsToReturn[currentIndex], err = jsonparser.ParseString(uuid); err != nil { - glog.Errorf("Prebid Cache response index %d could not be parsed as string: %v", currentIndex, err) - errs = append(errs, fmt.Errorf("Prebid Cache response index %d could not be parsed as string: %v", currentIndex, err)) + logError(&errs, "Prebid Cache response index %d could not be parsed as string: %v", currentIndex, err) uuidsToReturn[currentIndex] = "" } } @@ -146,17 +139,20 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s } if _, err := jsonparser.ArrayEach(responseBody, processResponse, "responses"); err != nil { - glog.Errorf("Error interpreting Prebid Cache response: %v\nResponse was: %s", err, string(responseBody)) - errs = append(errs, fmt.Errorf("Error interpreting Prebid Cache response: %v\nResponse was: %s", err, string(responseBody))) + logError(&errs, "Error interpreting Prebid Cache response: %v\nResponse was: %s", err, string(responseBody)) return uuidsToReturn, errs } return uuidsToReturn, errs } +func logError(errs *[]error, format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + glog.Error(msg) + *errs = append(*errs, errors.New(msg)) +} + func encodeValues(values []Cacheable) ([]byte, error) { - // This function assumes that m is non-nil and has at least one element. - // clientImp.PutBids should respect this. var buf bytes.Buffer buf.WriteString(`{"puts":[`) for i := 0; i < len(values); i++ { diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index d3b5ee4bfaf..1b5b4e38967 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "strconv" @@ -17,7 +18,6 @@ import ( "github.com/stretchr/testify/mock" ) -// Prevents #197 func TestEmptyPut(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Errorf("The server should not be called.") @@ -72,32 +72,70 @@ func TestBadResponse(t *testing.T) { } func TestCancelledContext(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + testCases := []struct { + description string + cacheable []Cacheable + expectedItems int + expectedPayloadSize int + }{ + { + description: "1 Item", + cacheable: []Cacheable{ + { + Type: TypeJSON, + Data: json.RawMessage("true"), + }, + }, + expectedItems: 1, + expectedPayloadSize: 39, + }, + { + description: "2 Items", + cacheable: []Cacheable{ + { + Type: TypeJSON, + Data: json.RawMessage("true"), + }, + { + Type: TypeJSON, + Data: json.RawMessage("false"), + }, + }, + expectedItems: 2, + expectedPayloadSize: 69, + }, + } + + // Initialize Stub Server + stubHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) - server := httptest.NewServer(handler) - defer server.Close() + stubServer := httptest.NewServer(stubHandler) + defer stubServer.Close() - metricsMock := &pbsmetrics.MetricsEngineMock{} - metricsMock.On("RecordPrebidCacheRequestTime", false, mock.Anything).Once() + // Run Tests + for _, testCase := range testCases { + metricsMock := &pbsmetrics.MetricsEngineMock{} + metricsMock.On("RecordPrebidCacheRequestTime", false, mock.Anything).Once() - client := &clientImpl{ - httpClient: server.Client(), - putUrl: server.URL, - metrics: metricsMock, - } + client := &clientImpl{ + httpClient: stubServer.Client(), + putUrl: stubServer.URL, + metrics: metricsMock, + } - ctx, cancel := context.WithCancel(context.Background()) - cancel() - ids, _ := client.PutJson(ctx, []Cacheable{{ - Type: TypeJSON, - Data: json.RawMessage("true"), - }, - }) - assertIntEqual(t, len(ids), 1) - assertStringEqual(t, ids[0], "") + ctx, cancel := context.WithCancel(context.Background()) + cancel() + ids, errs := client.PutJson(ctx, testCase.cacheable) - metricsMock.AssertExpectations(t) + expectedErrorMessage := fmt.Sprintf("Items=%v, Payload Size=%v", testCase.expectedItems, testCase.expectedPayloadSize) + + assert.Equal(t, testCase.expectedItems, len(ids), testCase.description+":ids") + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "Error sending the request to Prebid Cache: context canceled", testCase.description+":error") + assert.Contains(t, errs[0].Error(), expectedErrorMessage, testCase.description+":error_dimensions") + metricsMock.AssertExpectations(t) + } } func TestSuccessfulPut(t *testing.T) { From f75de9240d69d37857af96c13a649dcbc5c0b017 Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Tue, 28 Jan 2020 20:43:46 +0530 Subject: [PATCH 009/603] Fix PubMatic Usersync URL (#1178) Co-authored-by: pm-isha-bharti --- adapters/pubmatic/usersync_test.go | 12 ++++++++---- config/config.go | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/adapters/pubmatic/usersync_test.go b/adapters/pubmatic/usersync_test.go index ef81d223377..dd4a086c453 100644 --- a/adapters/pubmatic/usersync_test.go +++ b/adapters/pubmatic/usersync_test.go @@ -5,12 +5,13 @@ import ( "text/template" "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) func TestPubmaticSyncer(t *testing.T) { - syncURL := "//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" + syncURL := "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" syncURLTemplate := template.Must( template.New("sync-template").Parse(syncURL), ) @@ -18,13 +19,16 @@ func TestPubmaticSyncer(t *testing.T) { syncer := NewPubmaticSyncer(syncURLTemplate) syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Value: "C", }, }) assert.NoError(t, err) - assert.Equal(t, "//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D", syncInfo.URL) + assert.Equal(t, "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr=A&gdpr_consent=B&us_privacy=C&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) assert.EqualValues(t, 76, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) diff --git a/config/config.go b/config/config.go index 282ae9dc2b5..ba6ed05e339 100644 --- a/config/config.go +++ b/config/config.go @@ -513,7 +513,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") // openrtb_ext.BidderRTBHouse doesn't have a good default. From 5507b37d96dcfd1dfc2e47efc20bee254bca35b2 Mon Sep 17 00:00:00 2001 From: Corey Kress Date: Fri, 31 Jan 2020 14:09:07 -0500 Subject: [PATCH 010/603] [Synacormedia] Add tagId bidder parameter (#1165) --- adapters/synacormedia/params_test.go | 4 +- adapters/synacormedia/synacormedia.go | 19 ++- .../exemplary/simple-banner.json | 11 +- .../exemplary/simple-video.json | 15 +- .../synacormediatest/params/banner.json | 3 +- .../synacormediatest/params/video.json | 3 +- .../supplemental/audio_response.json | 11 +- .../supplemental/bad_response.json | 11 +- .../supplemental/bad_seat_id.json | 3 +- .../supplemental/missing_seat_id.json | 3 +- .../supplemental/missing_tag_id.json | 30 ++++ .../supplemental/native_response.json | 11 +- .../supplemental/one_bad_ext.json | 136 ++++++++++++++++++ .../supplemental/status_204.json | 11 +- .../supplemental/status_400.json | 11 +- .../supplemental/status_500.json | 11 +- openrtb_ext/imp_synacormedia.go | 1 + static/bidder-params/synacormedia.json | 4 + 18 files changed, 253 insertions(+), 45 deletions(-) create mode 100644 adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json create mode 100644 adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json diff --git a/adapters/synacormedia/params_test.go b/adapters/synacormedia/params_test.go index ffd891f4e84..a216818e382 100644 --- a/adapters/synacormedia/params_test.go +++ b/adapters/synacormedia/params_test.go @@ -40,9 +40,9 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"seatId": "123"}`, + `{"seatId": "123", "tagId":"234"}`, } var invalidParams = []string{ - `{"seatId": 123}`, + `{"seatId": 123, "tagId":234}`, } diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index ccb2798f8cf..2d913f026b4 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -20,6 +20,7 @@ type SynacorMediaAdapter struct { type SyncEndpointTemplateParams struct { SeatId string + TagId string } type ReqExt struct { @@ -55,14 +56,23 @@ func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapter var firstExtImp *openrtb_ext.ExtImpSynacormedia = nil for _, imp := range request.Imp { - validImp, err := getExtImpObj(&imp) + validExtImpObj, err := getExtImpObj(&imp) // getExtImpObj returns {seatId:"", tagId:""} if err != nil { errs = append(errs, err) continue } + // if the bid request is missing seatId or TagId then ignore it + if validExtImpObj.SeatId == "" || validExtImpObj.TagId == "" { + errs = append(errs, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid Impression"), + }) + continue + } + // right here is where we need to take out the tagId and then add it to imp + imp.TagID = validExtImpObj.TagId validImps = append(validImps, imp) if firstExtImp == nil { - firstExtImp = validImp + firstExtImp = validExtImpObj } } @@ -72,11 +82,12 @@ func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapter var err error - if firstExtImp == nil || firstExtImp.SeatId == "" { + if firstExtImp == nil || firstExtImp.SeatId == "" || firstExtImp.TagId == "" { return nil, append(errs, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Impression missing seat id"), + Message: fmt.Sprintf("Invalid Impression"), }) } + // this is where the empty seatId is filled re = &ReqExt{SeatId: firstExtImp.SeatId} // create JSON Request Body diff --git a/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json b/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json index 013891b6fa8..944e6e549ab 100644 --- a/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json +++ b/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json @@ -14,7 +14,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -24,15 +25,16 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "banner": { "format": [ {"w":300,"h":250} @@ -40,7 +42,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/exemplary/simple-video.json b/adapters/synacormedia/synacormediatest/exemplary/simple-video.json index f12556105db..2cddd5220f9 100644 --- a/adapters/synacormedia/synacormediatest/exemplary/simple-video.json +++ b/adapters/synacormedia/synacormediatest/exemplary/simple-video.json @@ -10,7 +10,6 @@ "imp": [ { "id": "i3", - "tagid": "3020", "video": { "w": 300, "h": 250, @@ -21,8 +20,9 @@ }, "metric": [], "ext": { - "bidder":{ - "seatId":"1927" + "bidder": { + "seatId":"prebid", + "tagId": "demo1" } } } @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "1", "site": { @@ -40,12 +40,12 @@ "publisher": {} }, "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id": "i3", - "tagid": "3020", + "tagid": "demo1", "video": { "w": 300, "h": 250, @@ -56,7 +56,8 @@ }, "ext": { "bidder":{ - "seatId":"1927" + "seatId":"prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/params/banner.json b/adapters/synacormedia/synacormediatest/params/banner.json index d6f4e7e9641..bb55ddfc48c 100644 --- a/adapters/synacormedia/synacormediatest/params/banner.json +++ b/adapters/synacormedia/synacormediatest/params/banner.json @@ -1,3 +1,4 @@ { - "seatId": "123" + "seatId": "123", + "tagId": "234" } diff --git a/adapters/synacormedia/synacormediatest/params/video.json b/adapters/synacormedia/synacormediatest/params/video.json index d6f4e7e9641..bb55ddfc48c 100644 --- a/adapters/synacormedia/synacormediatest/params/video.json +++ b/adapters/synacormedia/synacormediatest/params/video.json @@ -1,3 +1,4 @@ { - "seatId": "123" + "seatId": "123", + "tagId": "234" } diff --git a/adapters/synacormedia/synacormediatest/supplemental/audio_response.json b/adapters/synacormedia/synacormediatest/supplemental/audio_response.json index 172a81efa85..752d610aa72 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/audio_response.json +++ b/adapters/synacormedia/synacormediatest/supplemental/audio_response.json @@ -9,7 +9,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -19,21 +20,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "audio": { "mimes": ["video/mp4"] }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json index 0893598bd1d..8e8b9a4d944 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json +++ b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json @@ -18,7 +18,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -28,15 +29,16 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "banner": { "format": [ {"w":300,"h":250}, @@ -45,7 +47,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json b/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json index bb144f1c6db..00dd2c23707 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json +++ b/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json @@ -14,7 +14,8 @@ }, "ext": { "bidder": { - "seatId": 1927 + "seatId": 1927, + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json b/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json index a085b6e64f9..b85b88e4189 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json +++ b/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "tagId": "demo1" } } } @@ -22,7 +23,7 @@ "expectedMakeRequestsErrors": [ { - "value": "Impression missing seat id", + "value": "Invalid Impression", "comparison": "literal" } ] diff --git a/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json b/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json new file mode 100644 index 00000000000..2e1ef6ada65 --- /dev/null +++ b/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "seatId": "prebid" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid Impression", + "comparison": "literal" + } + ] +} diff --git a/adapters/synacormedia/synacormediatest/supplemental/native_response.json b/adapters/synacormedia/synacormediatest/supplemental/native_response.json index 89742d6e0df..1428ac1ccd3 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/native_response.json +++ b/adapters/synacormedia/synacormediatest/supplemental/native_response.json @@ -9,7 +9,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -19,21 +20,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "native": { "request": "" }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json b/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json new file mode 100644 index 00000000000..5749aadba98 --- /dev/null +++ b/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-bad", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "seatId": "", + "tagId": "" + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "seatId": "prebid", + "tagId": "demo1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", + "body": { + "id": "test-request-id", + "ext": { + "seatId": "prebid" + }, + "imp": [ + { + "id":"test-imp-id", + "tagid": "demo1", + "banner": { + "format": [ + {"w":300,"h":250} + ] + }, + "ext": { + "bidder": { + "seatId": "prebid", + "tagId": "demo1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "1", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 2.69, + "adomain": [ + "psacentral.org" + ], + "cid": "mock-crid", + "crid": "mock-cid", + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "synacormedia" + } + ], + "ext": { + "responsetimemillis": { + "synacormedia": 339 + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "psacentral.org" + ], + "cid": "mock-crid", + "crid": "mock-cid", + "ext": { + "prebid": { + "type": "banner" + } + }, + "id": "test-request-id", + "impid": "test-imp-id", + "price": 2.69 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Invalid Impression", + "comparison": "literal" + } + ] +} diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_204.json b/adapters/synacormedia/synacormediatest/supplemental/status_204.json index f53ff1ec918..76f97f9cdfa 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/status_204.json +++ b/adapters/synacormedia/synacormediatest/supplemental/status_204.json @@ -18,7 +18,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -28,15 +29,16 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "banner": { "format": [ {"w":300,"h":250}, @@ -45,7 +47,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_400.json b/adapters/synacormedia/synacormediatest/supplemental/status_400.json index a0667658e1d..1bb2cf6fa45 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/status_400.json +++ b/adapters/synacormedia/synacormediatest/supplemental/status_400.json @@ -18,7 +18,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -28,15 +29,16 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "banner": { "format": [ {"w":300,"h":250}, @@ -45,7 +47,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_500.json b/adapters/synacormedia/synacormediatest/supplemental/status_500.json index 125829c2328..37ca398e59e 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/status_500.json +++ b/adapters/synacormedia/synacormediatest/supplemental/status_500.json @@ -18,7 +18,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } @@ -28,15 +29,16 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://1927.technoratimedia.com/openrtb/bids/1927", + "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid", "body": { "id": "test-request-id", "ext": { - "seatId": "1927" + "seatId": "prebid" }, "imp": [ { "id":"test-imp-id", + "tagid": "demo1", "banner": { "format": [ {"w":300,"h":250}, @@ -45,7 +47,8 @@ }, "ext": { "bidder": { - "seatId": "1927" + "seatId": "prebid", + "tagId": "demo1" } } } diff --git a/openrtb_ext/imp_synacormedia.go b/openrtb_ext/imp_synacormedia.go index 1b044ceaa9c..af48c7dfd01 100644 --- a/openrtb_ext/imp_synacormedia.go +++ b/openrtb_ext/imp_synacormedia.go @@ -3,4 +3,5 @@ package openrtb_ext // ExtImpSynacormedia defines the contract for bidrequest.imp[i].ext.synacormedia type ExtImpSynacormedia struct { SeatId string `json:"seatId"` + TagId string `json:"tagId"` } diff --git a/static/bidder-params/synacormedia.json b/static/bidder-params/synacormedia.json index b2dff8faca1..8c74ada2e85 100644 --- a/static/bidder-params/synacormedia.json +++ b/static/bidder-params/synacormedia.json @@ -8,6 +8,10 @@ "seatId": { "type": "string", "description": "The seat id." + }, + "tagId": { + "type": "string", + "description": "The tag id." } }, From b8871e98fcae73332be1d2e48c96b09f27d9e87d Mon Sep 17 00:00:00 2001 From: Seba Perez Date: Fri, 31 Jan 2020 17:11:46 -0300 Subject: [PATCH 011/603] Remove all non-secure calls from eplanning adapter (#1179) --- adapters/eplanning/eplanning_test.go | 2 +- .../eplanning/eplanningtest/exemplary/simple-banner-2.json | 2 +- adapters/eplanning/eplanningtest/exemplary/simple-banner.json | 2 +- adapters/eplanning/eplanningtest/exemplary/two-banners.json | 4 ++-- .../eplanningtest/supplemental/banner-no-size-sends-1x1.json | 4 ++-- .../eplanningtest/supplemental/invalid-response-no-bids.json | 4 ++-- .../supplemental/invalid-response-unmarshall-error.json | 4 ++-- .../eplanningtest/supplemental/server-bad-request.json | 4 ++-- .../eplanningtest/supplemental/server-error-code.json | 4 ++-- .../eplanningtest/supplemental/server-no-content.json | 4 ++-- .../supplemental/site-domain-and-url-correctly-parsed.json | 4 ++-- config/config.go | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index d1219ce4eef..d2c331d456d 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -8,7 +8,7 @@ import ( ) func TestJsonSamples(t *testing.T) { - eplanningAdapter := NewEPlanningBidder(new(http.Client), "http://ads.us.e-planning.net/hb/1") + eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/hb/1") eplanningAdapter.testing = true adapterstest.RunJSONBidderTest(t, "eplanningtest", eplanningAdapter) } diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json index 596b061576f..f4c8fe0c273 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=300x250:300x250", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=300x250:300x250", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json index 21cbbdae7df..61b7878a8bf 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadun_itco_de:600x300&uid=2154987&ip=123.123.123.123", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadun_itco_de:600x300&uid=2154987&ip=123.123.123.123", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/two-banners.json b/adapters/eplanning/eplanningtest/exemplary/two-banners.json index 15639524207..fccc1a30e7e 100644 --- a/adapters/eplanning/eplanningtest/exemplary/two-banners.json +++ b/adapters/eplanning/eplanningtest/exemplary/two-banners.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300+300x250:300x250&ip=123.123.123.123", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300+300x250:300x250&ip=123.123.123.123", "body": {} }, "mockResponse": { @@ -120,4 +120,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json index 729115e55de..afa7b9532df 100644 --- a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json +++ b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcodenosize:1x1", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcodenosize:1x1", "body": {} }, "mockResponse": { @@ -67,4 +67,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json index 57db7023360..3bf45a16364 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -45,4 +45,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json index fc85a6b45c0..e8d88f17a5e 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -52,4 +52,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json index 052c0561095..421f47efe3b 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -55,4 +55,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json index 699968ce398..ceec970ba45 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -55,4 +55,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json index 9058699af3e..a2e444a9901 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -30,4 +30,4 @@ } ] } - \ No newline at end of file + diff --git a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json index 4ce8ef2f692..d889f48189c 100644 --- a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json +++ b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?r=pbs&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere&e=testadunitcode:600x300", + "uri": "https://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?r=pbs&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere&e=testadunitcode:600x300", "body": {} }, "mockResponse": { @@ -73,4 +73,4 @@ } ] } - \ No newline at end of file + diff --git a/config/config.go b/config/config.go index ba6ed05e339..2f302cc6328 100644 --- a/config/config.go +++ b/config/config.go @@ -683,7 +683,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.eplanning.endpoint", "http://ads.us.e-planning.net/hb/1") + v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") From 63e7e1df5b11335ed24fed786d6b060eb79d345d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 5 Feb 2020 14:00:30 -0800 Subject: [PATCH 012/603] Expose Cache HTTP Settings (#1184) --- config/config.go | 4 ++++ config/config_test.go | 7 +++++++ exchange/exchange_test.go | 2 +- prebid_cache_client/client.go | 9 ++------- prebid_cache_client/client_test.go | 4 +--- router/router.go | 18 ++++++++++++++---- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/config/config.go b/config/config.go index 2f302cc6328..943d18a95de 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,7 @@ type Configuration struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Client HTTPClient `mapstructure:"http_client"` + CacheClient HTTPClient `mapstructure:"http_client_cache"` AdminPort int `mapstructure:"admin_port"` EnableGzip bool `mapstructure:"enable_gzip"` // StatusResponse is the string which will be returned by the /status endpoint when things are OK. @@ -583,6 +584,9 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client_cache.max_idle_connections", 10) + v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) + v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.influxdb.host", "") diff --git a/config/config_test.go b/config/config_test.go index 182a46eef50..78630e071d9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -68,6 +68,10 @@ http_client: max_idle_connections: 500 max_idle_connections_per_host: 20 idle_connection_timeout_seconds: 30 +http_client_cache: + max_idle_connections: 1 + max_idle_connections_per_host: 2 + idle_connection_timeout_seconds: 3 currency_converter: fetch_url: https://currency.prebid.org fetch_interval_seconds: 1800 @@ -214,6 +218,9 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500) cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20) cmpInts(t, "http_client.idle_connection_timeout_seconds", cfg.Client.IdleConnTimeout, 30) + cmpInts(t, "http_client_cache.max_idle_connections", cfg.CacheClient.MaxIdleConns, 1) + cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) + cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 31dddae4c74..b8a3ae0eae2 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -170,7 +170,7 @@ func TestGetBidCacheInfo(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - e := NewExchange(server.Client(), pbc.NewClient(&cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ liveAdapters := []openrtb_ext.BidderName{bidderName} diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 58e2734ed25..a5730ce7914 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -47,14 +47,9 @@ type Cacheable struct { Key string } -func NewClient(conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client { +func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client { return &clientImpl{ - httpClient: &http.Client{ - Transport: &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 65, - }, - }, + httpClient: httpClient, putUrl: conf.GetBaseURL() + "/cache", externalCacheHost: extCache.Host, externalCachePath: extCache.Path, diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 1b5b4e38967..5840d4ea564 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -233,11 +233,9 @@ func TestStripCacheHostAndPath(t *testing.T) { }, } for _, test := range testInput { - //start client - cacheClient := NewClient(&inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{}) + cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{}) cHost, cPath := cacheClient.GetExtCacheData() - //assert assert.Equal(t, test.expectedHost, cHost) assert.Equal(t, test.expectedPath, cPath) } diff --git a/router/router.go b/router/router.go index 1994639110c..449ab65a448 100644 --- a/router/router.go +++ b/router/router.go @@ -183,7 +183,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r glog.Infof("Could not read certificates file: %s \n", readCertErr.Error()) } - theClient := &http.Client{ + generalHttpClient := &http.Client{ Transport: &http.Transport{ MaxIdleConns: cfg.Client.MaxIdleConns, MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, @@ -191,13 +191,22 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r TLSClientConfig: &tls.Config{RootCAs: certPool}, }, } + + cacheHttpClient := &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: cfg.CacheClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second, + }, + } + // Hack because of how legacy handles districtm legacyBidderList := openrtb_ext.BidderList() legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) // Metrics engine r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList) - db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, theClient, r.Router) + db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown @@ -223,10 +232,11 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) syncers := usersyncers.NewSyncerMap(cfg) - gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(syncers), theClient) + gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(syncers), generalHttpClient) exchanges = newExchangeMap(cfg) - theExchange := exchange.NewExchange(theClient, pbc.NewClient(&cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine), cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor) + cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) + theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor) openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) From 4ff04cced4bd494e4300269563d201a4d4632dd1 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Thu, 6 Feb 2020 11:19:45 -0800 Subject: [PATCH 013/603] Adding bid rejection messages to debug response (#1181) --- exchange/exchange.go | 43 +++++++----- exchange/exchange_test.go | 139 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 161 insertions(+), 21 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 3d9055ca8a6..8c6cac4cfcd 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "math/rand" "net/http" @@ -146,10 +147,14 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque //If includebrandcategory is present in ext then CE feature is on. if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { var err error - bidCategory, adapterBids, err = applyCategoryMapping(ctx, requestExt, adapterBids, *categoriesFetcher, targData) + var rejections []string + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, *categoriesFetcher, targData) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } + for _, message := range rejections { + errs = append(errs, errors.New(message)) + } } auc = newAuction(adapterBids, len(bidRequest.Imp)) @@ -340,7 +345,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ return bidResponse, err } -func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, error) { +func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -359,6 +364,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest var primaryAdServer string var publisher string var err error + var rejections []string var translateCategories = true if includeBrandCategory && brandCatExt.WithCategory { @@ -370,7 +376,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest //if ext.prebid.targeting.includebrandcategory present but primaryadserver/publisher not present then error out the request right away. primaryAdServer, err = getPrimaryAdServer(brandCatExt.PrimaryAdServer) //1-Freewheel 2-DFP if err != nil { - return res, seatBids, err + return res, seatBids, rejections, err } publisher = brandCatExt.Publisher } @@ -382,6 +388,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest bidsToRemove := make([]int, 0) for bidInd := range seatBid.bids { bid := seatBid.bids[bidInd] + bidID := bid.bid.ID var duration int var category string var pb string @@ -396,6 +403,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest //TODO: add metrics //on receiving bids from adapters if no unique IAB category is returned or if no ad server category is returned discard the bid bidsToRemove = append(bidsToRemove, bidInd) + rejections = updateRejections(rejections, bidID, "Bid did not contain a category") continue } if translateCategories { @@ -405,6 +413,8 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest //TODO: add metrics //if mapping required but no mapping file is found then discard the bid bidsToRemove = append(bidsToRemove, bidInd) + reason := fmt.Sprintf("Category mapping file for primary ad server: '%s', publisher: '%s' not found", primaryAdServer, publisher) + rejections = updateRejections(rejections, bidID, reason) continue } } else { @@ -424,6 +434,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest //if the bid is above the range of the listed durations (and outside the buffer), reject the bid if duration > durationRange[len(durationRange)-1] { bidsToRemove = append(bidsToRemove, bidInd) + rejections = updateRejections(rejections, bidID, "Bid duration exceeds maximum allowed") continue } for _, dur := range durationRange { @@ -447,11 +458,13 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest if dupe.bidderName == bidderName { // An older bid from the current bidder bidsToRemove = append(bidsToRemove, dupe.bidIndex) + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { // An older bid from a different seatBid we've already finished with oldSeatBid := (seatBids)[dupe.bidderName] if len(oldSeatBid.bids) == 1 { seatBidsToRemove = append(seatBidsToRemove, bidderName) + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) } @@ -460,11 +473,12 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest } else { // Remove this bid bidsToRemove = append(bidsToRemove, bidInd) + rejections = updateRejections(rejections, bidID, "Bid was deduplicated") continue } } - res[bid.bid.ID] = categoryDuration - dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bid.bid.ID} + res[bidID] = categoryDuration + dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID} } if len(bidsToRemove) > 0 { @@ -483,19 +497,16 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest } } - if len(seatBidsToRemove) > 0 { - if len(seatBidsToRemove) == len(seatBids) { - //delete all seat bids - seatBids = nil - } else { - for _, seatBidInd := range seatBidsToRemove { - delete(seatBids, seatBidInd) - } - - } + for _, seatBidInd := range seatBidsToRemove { + seatBids[seatBidInd].bids = nil } - return res, seatBids, nil + return res, seatBids, rejections, nil +} + +func updateRejections(rejections []string, bidID string, reason string) []string { + message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason) + return append(rejections, message) } func getPrimaryAdServer(adServerId int) (string, error) { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index b8a3ae0eae2..7e199d4b750 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "regexp" "strconv" "strings" "testing" @@ -930,9 +931,11 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") + assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected") assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match") assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match") assert.Equal(t, "20.00_AdapterOverride_30s", bidCategory["bid_id3"], "Category mapping override from adapter didn't take") @@ -983,9 +986,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Empty(t, rejections, "There should be no bid rejection messages") assert.Equal(t, "10.00_30s", bidCategory["bid_id1"], "Category mapping doesn't match") assert.Equal(t, "20.00_40s", bidCategory["bid_id2"], "Category mapping doesn't match") assert.Equal(t, "20.00_30s", bidCategory["bid_id3"], "Category mapping doesn't match") @@ -1034,9 +1038,11 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") + assert.Equal(t, "bid rejected [bid ID: bid_id3] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected") assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match") assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match") assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match") @@ -1114,9 +1120,10 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Empty(t, rejections, "There should be no bid rejection messages") assert.Equal(t, "10.00_IAB1-3_30s", bidCategory["bid_id1"], "Category should not be translated") assert.Equal(t, "20.00_IAB1-4_50s", bidCategory["bid_id2"], "Category should not be translated") assert.Equal(t, "20.00_IAB1-1000_30s", bidCategory["bid_id3"], "Bid should not be rejected") @@ -1179,9 +1186,12 @@ func TestCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") + assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") + assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected") assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match") assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match") @@ -1196,6 +1206,125 @@ func TestCategoryDedupe(t *testing.T) { assert.NotEqual(t, numIterations, selectedBids["bid_id3"], "Bid 3 made it through every time") } +func TestBidRejectionErrors(t *testing.T) { + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + requestExt := newExtRequest() + requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50} + + targData := &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: true, + } + + invalidReqExt := newExtRequest() + invalidReqExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50} + invalidReqExt.Prebid.Targeting.IncludeBrandCategory.PrimaryAdServer = 2 + invalidReqExt.Prebid.Targeting.IncludeBrandCategory.Publisher = "some_publisher" + + adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + bidderName := openrtb_ext.BidderName("appnexus") + + testCases := []struct { + description string + reqExt openrtb_ext.ExtRequest + bids []*openrtb.Bid + duration int + expectedRejections []string + expectedCatDur string + }{ + { + description: "Bid should be rejected due to not containing a category", + reqExt: requestExt, + bids: []*openrtb.Bid{ + {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{}, W: 1, H: 1}, + }, + duration: 30, + expectedRejections: []string{ + "bid rejected [bid ID: bid_id1] reason: Bid did not contain a category", + }, + }, + { + description: "Bid should be rejected due to missing category mapping file", + reqExt: invalidReqExt, + bids: []*openrtb.Bid{ + {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, + }, + duration: 30, + expectedRejections: []string{ + "bid rejected [bid ID: bid_id1] reason: Category mapping file for primary ad server: 'dfp', publisher: 'some_publisher' not found", + }, + }, + { + description: "Bid should be rejected due to duration exceeding maximum", + reqExt: requestExt, + bids: []*openrtb.Bid{ + {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, + }, + duration: 70, + expectedRejections: []string{ + "bid rejected [bid ID: bid_id1] reason: Bid duration exceeds maximum allowed", + }, + }, + { + description: "Bid should be rejected due to duplicate bid", + reqExt: requestExt, + bids: []*openrtb.Bid{ + {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, + {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, + }, + duration: 30, + expectedRejections: []string{ + "bid rejected [bid ID: bid_id1] reason: Bid was deduplicated", + }, + expectedCatDur: "10.00_VideoGames_30s", + }, + } + + for _, test := range testCases { + innerBids := []*pbsOrtbBid{} + for _, bid := range test.bids { + currentBid := pbsOrtbBid{ + bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, + } + innerBids = append(innerBids, ¤tBid) + } + + seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + + adapterBids[bidderName] = &seatBid + + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, test.reqExt, adapterBids, categoriesFetcher, targData) + + if len(test.expectedCatDur) > 0 { + // Bid deduplication case + assert.Equal(t, 1, len(adapterBids[bidderName].bids), "Bidders number doesn't match") + assert.Equal(t, 1, len(bidCategory), "Bidders category mapping doesn't match") + assert.Equal(t, test.expectedCatDur, bidCategory["bid_id1"], "Bid category did not contain expected hb_pb_cat_dur") + } else { + assert.Empty(t, adapterBids[bidderName].bids, "Bidders number doesn't match") + assert.Empty(t, bidCategory, "Bidders category mapping doesn't match") + } + + assert.Empty(t, err, "Category mapping error should be empty") + assert.Equal(t, test.expectedRejections, rejections, test.description) + } +} + +func TestUpdateRejections(t *testing.T) { + rejections := []string{} + + rejections = updateRejections(rejections, "bid_id1", "some reason 1") + rejections = updateRejections(rejections, "bid_id2", "some reason 2") + + assert.Equal(t, 2, len(rejections), "Rejections should contain 2 rejection messages") + assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id1] reason: some reason 1", "Rejection message did not match expected") + assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id2] reason: some reason 2", "Rejection message did not match expected") +} + type exchangeSpec struct { IncomingRequest exchangeRequest `json:"incomingRequest"` OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` From fd9bc8f11aa528ef900bd9fd9f63c165acd93a40 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Mon, 10 Feb 2020 16:38:49 -0500 Subject: [PATCH 014/603] Adds timeout notifications for Facebook (#1182) --- adapters/adpone/adpone.go | 3 +- adapters/audienceNetwork/facebook.go | 17 +++++++++++ adapters/audienceNetwork/facebook_test.go | 37 +++++++++++++++++++++++ adapters/bidder.go | 13 ++++++++ exchange/bidder.go | 24 +++++++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index 345a4988580..b1822a0ac07 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -3,9 +3,10 @@ package adpone import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 706673cbafc..3ece7bb99e4 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -447,3 +447,20 @@ func NewFacebookBidder(client *http.Client, platformID string, appSecret string) appSecret: appSecret, } } + +func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { + // Note, facebook creates one request per imp, so all these requests will only have one imp in them + auction_id, err := jsonparser.GetString(req.Body, "imp", "[0]", "id") + if err != nil { + return &adapters.RequestData{}, []error{err} + } + + uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, fa.platformID, auction_id) + timeoutReq := adapters.RequestData{ + Method: "GET", + Uri: uri, + Body: nil, + Headers: http.Header{}, + } + return &timeoutReq, nil +} diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 2ce0ef3ba64..1edaabd45d7 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -4,7 +4,9 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/stretchr/testify/assert" ) type tagInfo struct { @@ -40,3 +42,38 @@ type FacebookExt struct { func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder(nil, "test-platform-id", "test-app-secret")) } + +func TestMakeTimeoutNotice(t *testing.T) { + req := adapters.RequestData{ + Body: []byte(`{"imp":[{"id":"1234"}]}}`), + } + fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + + tb, ok := fba.(adapters.TimeoutBidder) + if !ok { + t.Error("Facebook adapter is not a TimeoutAdapter") + } + + toReq, err := tb.MakeTimeoutNotification(&req) + assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err) + expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=test-platform-id&auction=1234&ortb_loss_code=2" + assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.") + +} + +func TestMakeTimeoutNoticeBadRequest(t *testing.T) { + req := adapters.RequestData{ + Body: []byte(`{"imp":[{{"id":"1234"}}`), + } + fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + + tb, ok := fba.(adapters.TimeoutBidder) + if !ok { + t.Error("Facebook adapter is not a TimeoutAdapter") + } + + toReq, err := tb.MakeTimeoutNotification(&req) + assert.Empty(t, toReq.Uri, "Facebook MakeTimeoutNotification() did not return nil", err) + assert.NotNil(t, err, "Facebook MakeTimeoutNotification() did not return an error") + +} diff --git a/adapters/bidder.go b/adapters/bidder.go index 9d3ffb75414..baec4135b6a 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -39,6 +39,19 @@ type Bidder interface { MakeBids(internalRequest *openrtb.BidRequest, externalRequest *RequestData, response *ResponseData) (*BidderResponse, []error) } +// TimeoutBidder is used to identify bidders that support timeout notifications. +type TimeoutBidder interface { + Bidder + + // MakeTimeoutNotice functions much the same as MakeRequests, except it is fed the bidder request that timed out, + // and expects that only one notification "request" will be generated. A use case for multiple timeout notifications + // has not been anticipated. + // + // Do note that if MakeRequests returns multiple requests, and more than one of these times out, MakeTimeoutNotice will be called + // once for each timed out request. + MakeTimeoutNotification(req *RequestData) (*RequestData, []error) +} + type MisconfiguredBidder struct { Name string Error error diff --git a/exchange/bidder.go b/exchange/bidder.go index 5708660057f..d9a28fee175 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "net/http" + "time" "github.com/mxmCherry/openrtb" nativeRequests "github.com/mxmCherry/openrtb/native/request" @@ -295,6 +296,14 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques if err != nil { if err == context.DeadlineExceeded { err = &errortypes.Timeout{Message: err.Error()} + if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); ok { + // Toss the timeout notification call into a go routine, as we are out of time' + // and cannot delay processing. We don't do anything result, as there is not much + // we can do about a timeout notification failure. We do not want to get stuck in + // a loop of trying to report timeouts to the timeout notifications. + go bidder.doTimeoutNotification(tb, req) + } + } return &httpCallInfo{ request: req, @@ -328,6 +337,21 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques } } +func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData) { + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + toReq, errL := timeoutBidder.MakeTimeoutNotification(req) + if toReq != nil && len(errL) == 0 { + httpReq, err := http.NewRequest(toReq.Method, toReq.Uri, bytes.NewBuffer(toReq.Body)) + if err == nil { + httpReq.Header = req.Headers + ctxhttp.Do(ctx, bidder.Client, httpReq) + // No validation yet on sending notifications + } + } + +} + type httpCallInfo struct { request *adapters.RequestData response *adapters.ResponseData From 7762c0c3a926c07b24ef50605545a4987cd4e280 Mon Sep 17 00:00:00 2001 From: Michael Kuryshev Date: Tue, 18 Feb 2020 18:35:49 +0100 Subject: [PATCH 015/603] VIS.X: added app type support (#1194) --- static/bidder-info/visx.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index dd4f6c660de..f404a013337 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -4,3 +4,6 @@ capabilities: site: mediaTypes: - banner + app: + mediaTypes: + - banner From 8e382e7b8c12b66e4ce6392078b00c2151ce6f32 Mon Sep 17 00:00:00 2001 From: Viacheslav Chimishuk Date: Fri, 21 Feb 2020 20:14:53 +0200 Subject: [PATCH 016/603] Add Adoppler bidder support. (#1186) * Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter. --- adapters/adoppler/adoppler.go | 210 ++++++++++++++++++ adapters/adoppler/adoppler_test.go | 12 + .../adopplertest/exemplary/multibid.json | 60 +++++ .../adopplertest/exemplary/no-bid.json | 13 ++ .../supplemental/bad-request.json | 15 ++ .../supplemental/duplicate-imp.json | 38 ++++ .../supplemental/invalid-impid.json | 20 ++ .../supplemental/invalid-response.json | 15 ++ .../supplemental/invalid-video-ext.json | 43 ++++ .../supplemental/missing-adunit.json | 9 + .../supplemental/server-error.json | 15 ++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adoppler.go | 5 + static/bidder-info/adoppler.yaml | 11 + static/bidder-params/adoppler.json | 13 ++ usersync/usersyncers/syncer_test.go | 1 + 18 files changed, 485 insertions(+) create mode 100644 adapters/adoppler/adoppler.go create mode 100644 adapters/adoppler/adoppler_test.go create mode 100644 adapters/adoppler/adopplertest/exemplary/multibid.json create mode 100644 adapters/adoppler/adopplertest/exemplary/no-bid.json create mode 100644 adapters/adoppler/adopplertest/supplemental/bad-request.json create mode 100644 adapters/adoppler/adopplertest/supplemental/duplicate-imp.json create mode 100644 adapters/adoppler/adopplertest/supplemental/invalid-impid.json create mode 100644 adapters/adoppler/adopplertest/supplemental/invalid-response.json create mode 100644 adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json create mode 100644 adapters/adoppler/adopplertest/supplemental/missing-adunit.json create mode 100644 adapters/adoppler/adopplertest/supplemental/server-error.json create mode 100644 openrtb_ext/imp_adoppler.go create mode 100644 static/bidder-info/adoppler.yaml create mode 100644 static/bidder-params/adoppler.json diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go new file mode 100644 index 00000000000..c604bfeac06 --- /dev/null +++ b/adapters/adoppler/adoppler.go @@ -0,0 +1,210 @@ +package adoppler + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +var bidHeaders http.Header = map[string][]string{ + "Accept": {"application/json"}, + "Content-Type": {"application/json;charset=utf-8"}, + "X-OpenRTB-Version": {"2.5"}, +} + +type adsVideoExt struct { + Duration int `json:"duration"` +} + +type adsImpExt struct { + Video *adsVideoExt `json:"video"` +} + +type AdopplerAdapter struct { + endpoint string +} + +func NewAdopplerBidder(endpoint string) *AdopplerAdapter { + return &AdopplerAdapter{endpoint} +} + +func (ads *AdopplerAdapter) MakeRequests( + req *openrtb.BidRequest, + info *adapters.ExtraRequestInfo, +) ( + []*adapters.RequestData, + []error, +) { + if len(req.Imp) == 0 { + return nil, nil + } + + var datas []*adapters.RequestData + var errs []error + for _, imp := range req.Imp { + ext, err := unmarshalExt(imp.Ext) + if err != nil { + errs = append(errs, &errortypes.BadInput{err.Error()}) + continue + } + + var r openrtb.BidRequest = *req + r.ID = req.ID + "-" + ext.AdUnit + r.Imp = []openrtb.Imp{imp} + + body, err := json.Marshal(r) + if err != nil { + errs = append(errs, err) + continue + } + + uri := fmt.Sprintf("%s/processHeaderBid/%s", + ads.endpoint, url.PathEscape(ext.AdUnit)) + data := &adapters.RequestData{ + Method: "POST", + Uri: uri, + Body: body, + Headers: bidHeaders, + } + datas = append(datas, data) + } + + return datas, errs +} + +func (ads *AdopplerAdapter) MakeBids( + intReq *openrtb.BidRequest, + extReq *adapters.RequestData, + resp *adapters.ResponseData, +) ( + *adapters.BidderResponse, + []error, +) { + if resp.StatusCode == http.StatusNoContent { + return nil, nil + } + if resp.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{"bad request"}} + } + if resp.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + fmt.Sprintf("unexpected status: %d", resp.StatusCode), + } + return nil, []error{err} + } + + var bidResp openrtb.BidResponse + err := json.Unmarshal(resp.Body, &bidResp) + if err != nil { + err := &errortypes.BadServerResponse{ + fmt.Sprintf("invalid body: %s", err.Error()), + } + return nil, []error{err} + } + + impTypes := make(map[string]openrtb_ext.BidType) + for _, imp := range intReq.Imp { + if _, ok := impTypes[imp.ID]; ok { + return nil, []error{&errortypes.BadInput{ + fmt.Sprintf("duplicate $.imp.id %s", imp.ID), + }} + } + if imp.Banner != nil { + impTypes[imp.ID] = openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + impTypes[imp.ID] = openrtb_ext.BidTypeVideo + } else if imp.Audio != nil { + impTypes[imp.ID] = openrtb_ext.BidTypeAudio + } else if imp.Native != nil { + impTypes[imp.ID] = openrtb_ext.BidTypeNative + } else { + return nil, []error{&errortypes.BadInput{ + "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", + }} + } + } + + var bids []*adapters.TypedBid + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + tp, ok := impTypes[bid.ImpID] + if !ok { + err := &errortypes.BadServerResponse{ + fmt.Sprintf("unknown impid: %s", bid.ImpID), + } + return nil, []error{err} + } + + var bidVideo *openrtb_ext.ExtBidPrebidVideo + if tp == openrtb_ext.BidTypeVideo { + adsExt, err := unmarshalAdsExt(bid.Ext) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{err.Error()}} + } + if adsExt == nil || adsExt.Video == nil { + return nil, []error{&errortypes.BadServerResponse{ + "$.seatbid.bid.ext.ads.video required", + }} + } + bidVideo = &openrtb_ext.ExtBidPrebidVideo{ + Duration: adsExt.Video.Duration, + PrimaryCategory: head(bid.Cat), + } + } + bids = append(bids, &adapters.TypedBid{ + Bid: &bid, + BidType: tp, + BidVideo: bidVideo, + }) + } + } + + adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids)) + adsResp.Bids = bids + + return adsResp, nil +} + +func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { + var bext adapters.ExtImpBidder + err := json.Unmarshal(ext, &bext) + if err != nil { + return nil, err + } + + var adsExt openrtb_ext.ExtImpAdoppler + err = json.Unmarshal(bext.Bidder, &adsExt) + if err != nil { + return nil, err + } + + if adsExt.AdUnit == "" { + return nil, errors.New("$.imp.ext.adoppler.adunit required") + } + + return &adsExt, nil +} + +func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) { + var e struct { + Ads *adsImpExt `json:"ads"` + } + err := json.Unmarshal(ext, &e) + + return e.Ads, err +} + +func head(s []string) string { + if len(s) == 0 { + return "" + } + + return s[0] +} diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go new file mode 100644 index 00000000000..e7d908df4f1 --- /dev/null +++ b/adapters/adoppler/adoppler_test.go @@ -0,0 +1,12 @@ +package adoppler + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + bidder := NewAdopplerBidder("http://adoppler.com") + adapterstest.RunJSONBidderTest(t, "adopplertest", bidder) +} diff --git a/adapters/adoppler/adopplertest/exemplary/multibid.json b/adapters/adoppler/adopplertest/exemplary/multibid.json new file mode 100644 index 00000000000..851f4c5b917 --- /dev/null +++ b/adapters/adoppler/adopplertest/exemplary/multibid.json @@ -0,0 +1,60 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}, + {"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}, + {"id": "imp3", + "native": {"request": "{}"}, + "ext": {"bidder": {"adunit": "unit3"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", + "body": {"id": "req1-unit2", + "imp": [{"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp2-resp2", + "seatbid": [{"bid": [{"id": "req1-imp2-bid1", + "impid": "imp2", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": {"ads": {"video": {"duration": 121}}}}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit3", + "body": {"id": "req1-unit3", + "imp": [{"id": "imp3", + "native": {"request": "{}"}, + "ext": {"bidder": {"adunit": "unit3"}}}]}}, + "mockResponse": {"status": 204, + "body": ""}}], + "expectedBidResponses": [{"currency": "USD", + "bids": [{"bid": {"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}, + "type": "banner"}]}, + {"currency": "USD", + "bids": [{"bid": {"id": "req1-imp2-bid1", + "impid": "imp2", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": {"ads": {"video": {"duration": 121}}}}, + "type": "video"}]}]} diff --git a/adapters/adoppler/adopplertest/exemplary/no-bid.json b/adapters/adoppler/adopplertest/exemplary/no-bid.json new file mode 100644 index 00000000000..0e0f13586a8 --- /dev/null +++ b/adapters/adoppler/adopplertest/exemplary/no-bid.json @@ -0,0 +1,13 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 204, + "body": ""}}], + "expectedBidResponses": []} diff --git a/adapters/adoppler/adopplertest/supplemental/bad-request.json b/adapters/adoppler/adopplertest/supplemental/bad-request.json new file mode 100644 index 00000000000..3bdd5a5544e --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/bad-request.json @@ -0,0 +1,15 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 400, + "body": ""}}], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{"value": "bad request", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json new file mode 100644 index 00000000000..4382e36c54e --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json @@ -0,0 +1,38 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}, + {"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit2"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", + "body": {"id": "req1-unit2", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit2"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}]}], + "cur": "USD"}}}], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{"value": "duplicate $.imp.id imp1", + "comparison": "literal"}, + {"value": "duplicate $.imp.id imp1", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-impid.json b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json new file mode 100644 index 00000000000..2e6ecf4a96c --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json @@ -0,0 +1,20 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "invalid", + "price": 0.12, + "adm": "a banner"}]}], + "cur": "USD"}}}], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{"value": "unknown impid: invalid", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-response.json b/adapters/adoppler/adopplertest/supplemental/invalid-response.json new file mode 100644 index 00000000000..72420881aec --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/invalid-response.json @@ -0,0 +1,15 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": "invalid-json"}}], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{"value": "invalid body: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json new file mode 100644 index 00000000000..d9cb6daa55d --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json @@ -0,0 +1,43 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit1"}}}, + {"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": {}}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", + "body": {"id": "req1-unit2", + "imp": [{"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp2-resp2", + "seatbid": [{"bid": [{"id": "req1-imp2-bid2", + "impid": "imp2", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": ""}]}], + "cur": "USD"}}}], + "expectedMakeBidsErrors": [{"value": "$.seatbid.bid.ext.ads.video required", + "comparison": "literal"}, + {"value": "json: cannot unmarshal string into Go value of type struct { Ads *adoppler.adsImpExt \"json:\\\"ads\\\"\" }", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/missing-adunit.json b/adapters/adoppler/adopplertest/supplemental/missing-adunit.json new file mode 100644 index 00000000000..82a6a95ed58 --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/missing-adunit.json @@ -0,0 +1,9 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {}}}]}, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [{"value": "$.imp.ext.adoppler.adunit required", + "comparison": "literal"}]} diff --git a/adapters/adoppler/adopplertest/supplemental/server-error.json b/adapters/adoppler/adopplertest/supplemental/server-error.json new file mode 100644 index 00000000000..df23bac07df --- /dev/null +++ b/adapters/adoppler/adopplertest/supplemental/server-error.json @@ -0,0 +1,15 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 500, + "body": ""}}], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{"value": "unexpected status: 500", + "comparison": "literal"}]} diff --git a/config/config.go b/config/config.go index 943d18a95de..52686422039 100644 --- a/config/config.go +++ b/config/config.go @@ -673,6 +673,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}") + v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 95f5b7f5882..d169c1204bf 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -12,6 +12,7 @@ import ( "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" + "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" @@ -71,6 +72,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdform: adform.NewAdformBidder(client, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), openrtb_ext.BidderAdkernel: adkernel.NewAdkernelAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernel))].Endpoint), openrtb_ext.BidderAdkernelAdn: adkernelAdn.NewAdkernelAdnAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernelAdn))].Endpoint), + openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), openrtb_ext.BidderAdpone: adpone.NewAdponeBidder(cfg.Adapters[string(openrtb_ext.BidderAdpone)].Endpoint), openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), openrtb_ext.BidderAdvangelists: advangelists.NewAdvangelistsBidder(cfg.Adapters[string(openrtb_ext.BidderAdvangelists)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7a3f24eb07f..6e70ef4b6fa 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -29,6 +29,7 @@ const ( BidderAdvangelists BidderName = "advangelists" BidderApplogy BidderName = "applogy" BidderAppnexus BidderName = "appnexus" + BidderAdoppler BidderName = "adoppler" BidderBeachfront BidderName = "beachfront" BidderBrightroll BidderName = "brightroll" BidderConsumable BidderName = "consumable" @@ -84,6 +85,7 @@ var BidderMap = map[string]BidderName{ "advangelists": BidderAdvangelists, "applogy": BidderApplogy, "appnexus": BidderAppnexus, + "adoppler": BidderAdoppler, "beachfront": BidderBeachfront, "brightroll": BidderBrightroll, "consumable": BidderConsumable, diff --git a/openrtb_ext/imp_adoppler.go b/openrtb_ext/imp_adoppler.go new file mode 100644 index 00000000000..4b3ba97ce05 --- /dev/null +++ b/openrtb_ext/imp_adoppler.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAdoppler struct { + AdUnit string `json:"adunit"` +} diff --git a/static/bidder-info/adoppler.yaml b/static/bidder-info/adoppler.yaml new file mode 100644 index 00000000000..7fa79eda163 --- /dev/null +++ b/static/bidder-info/adoppler.yaml @@ -0,0 +1,11 @@ +maintainer: + email: info@adoppler.com +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/adoppler.json b/static/bidder-params/adoppler.json new file mode 100644 index 00000000000..c2bdde4f60f --- /dev/null +++ b/static/bidder-params/adoppler.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adoppler Adapter Params", + "description": "A schema which validates params accepted by the Adoppler adapter", + "type": "object", + "properties": { + "adunit": { + "type": "string", + "description": "AdUnit to bid against to." + } + }, + "required": ["adunit"] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index ded8fd2bd78..87a9caebf96 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -65,6 +65,7 @@ func TestNewSyncerMap(t *testing.T) { } adaptersWithoutSyncers := map[openrtb_ext.BidderName]bool{ + openrtb_ext.BidderAdoppler: true, openrtb_ext.BidderApplogy: true, openrtb_ext.BidderTappx: true, openrtb_ext.BidderKubient: true, From fd78c23caef9986556b8558443a7e0cb91831a3a Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Wed, 26 Feb 2020 15:54:37 -0800 Subject: [PATCH 017/603] Adding support for deal prefixes (#1183) --- adapters/appnexus/appnexus.go | 8 +- .../exemplary/simple-auction.json | 6 +- .../video/simple-video.json | 6 +- .../appnexustest/amp/simple-banner.json | 6 +- .../appnexustest/amp/simple-video.json | 6 +- .../appnexustest/exemplary/native-1.1.json | 6 +- .../appnexustest/exemplary/simple-banner.json | 6 +- .../appnexustest/exemplary/simple-video.json | 6 +- .../exemplary/video-invalid-category.json | 6 +- .../supplemental/displaymanager-test.json | 6 +- .../appnexustest/supplemental/multi-bid.json | 12 +- adapters/bidder.go | 8 +- endpoints/openrtb2/video_auction.go | 5 +- exchange/bidder.go | 17 +- exchange/bidder_test.go | 9 +- exchange/exchange.go | 82 +++++ exchange/exchange_test.go | 296 ++++++++++++++++-- openrtb_ext/bid_request_video.go | 8 + openrtb_ext/request.go | 1 + 19 files changed, 442 insertions(+), 58 deletions(-) diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 3986bfd45b0..9bec9bf1e3b 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -87,6 +87,7 @@ type appnexusBidExtAppnexus struct { BrandId int `json:"brand_id"` BrandCategory int `json:"brand_category_id"` CreativeInfo appnexusBidExtCreative `json:"creative_info"` + DealPriority int `json:"deal_priority"` } type appnexusBidExt struct { @@ -543,9 +544,10 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: bidType, - BidVideo: impVideo, + Bid: &bid, + BidType: bidType, + BidVideo: impVideo, + DealPriority: bidExt.Appnexus.DealPriority, }) } else { errs = append(errs, err) diff --git a/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json b/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json index 03c3f4c5880..e0c0435faab 100644 --- a/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json +++ b/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json @@ -80,7 +80,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -118,7 +119,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexusplatformtest/video/simple-video.json b/adapters/appnexus/appnexusplatformtest/video/simple-video.json index 85960427d81..7ee192be2c1 100644 --- a/adapters/appnexus/appnexusplatformtest/video/simple-video.json +++ b/adapters/appnexus/appnexusplatformtest/video/simple-video.json @@ -80,7 +80,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -118,7 +119,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/amp/simple-banner.json b/adapters/appnexus/appnexustest/amp/simple-banner.json index 646359b4267..54e6a143e19 100644 --- a/adapters/appnexus/appnexustest/amp/simple-banner.json +++ b/adapters/appnexus/appnexustest/amp/simple-banner.json @@ -91,7 +91,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -129,7 +130,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/amp/simple-video.json b/adapters/appnexus/appnexustest/amp/simple-video.json index a6f96be34b8..061d5c94369 100644 --- a/adapters/appnexus/appnexustest/amp/simple-video.json +++ b/adapters/appnexus/appnexustest/amp/simple-video.json @@ -82,7 +82,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -120,7 +121,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/exemplary/native-1.1.json b/adapters/appnexus/appnexustest/exemplary/native-1.1.json index 86b75505e0c..189304fdb4c 100644 --- a/adapters/appnexus/appnexustest/exemplary/native-1.1.json +++ b/adapters/appnexus/appnexustest/exemplary/native-1.1.json @@ -96,7 +96,8 @@ "brand_category_id": 350, "auction_id": 5607483846416358664, "bidder_id": 2, - "bid_ad_type": 3 + "bid_ad_type": 3, + "deal_priority": 5 } } } @@ -136,7 +137,8 @@ "brand_category_id": 350, "auction_id": 5607483846416358664, "bidder_id": 2, - "bid_ad_type": 3 + "bid_ad_type": 3, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/exemplary/simple-banner.json b/adapters/appnexus/appnexustest/exemplary/simple-banner.json index e5bd311648f..59931fb6ad7 100644 --- a/adapters/appnexus/appnexustest/exemplary/simple-banner.json +++ b/adapters/appnexus/appnexustest/exemplary/simple-banner.json @@ -89,7 +89,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -127,7 +128,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/exemplary/simple-video.json b/adapters/appnexus/appnexustest/exemplary/simple-video.json index 15755c7de37..ced90c39549 100644 --- a/adapters/appnexus/appnexustest/exemplary/simple-video.json +++ b/adapters/appnexus/appnexustest/exemplary/simple-video.json @@ -80,7 +80,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -118,7 +119,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json b/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json index d3686af00a9..257905c873f 100644 --- a/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json +++ b/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json @@ -79,7 +79,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -116,7 +117,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 1, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json b/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json index d5c981c6945..c6ad330e3a8 100644 --- a/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json +++ b/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json @@ -106,7 +106,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -144,7 +145,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/appnexus/appnexustest/supplemental/multi-bid.json b/adapters/appnexus/appnexustest/supplemental/multi-bid.json index 7234551ea3f..9e63bdced95 100644 --- a/adapters/appnexus/appnexustest/supplemental/multi-bid.json +++ b/adapters/appnexus/appnexustest/supplemental/multi-bid.json @@ -89,7 +89,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 4 } } }, @@ -112,7 +113,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }] @@ -150,7 +152,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 4 } } }, @@ -177,7 +180,8 @@ "auction_id": 8189378542222915032, "bid_ad_type": 0, "bidder_id": 2, - "ranking_price": 0.000000 + "ranking_price": 0.000000, + "deal_priority": 5 } } }, diff --git a/adapters/bidder.go b/adapters/bidder.go index baec4135b6a..627caf67344 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -108,10 +108,12 @@ func NewBidderResponse() *BidderResponse { // TypedBid.Bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response. // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response. +// TypedBid.DealPriority will become "response.seatbid[i].bid.dealPriority" in the final OpenRTB response. type TypedBid struct { - Bid *openrtb.Bid - BidType openrtb_ext.BidType - BidVideo *openrtb_ext.ExtBidPrebidVideo + Bid *openrtb.Bid + BidType openrtb_ext.BidType + BidVideo *openrtb_ext.ExtBidPrebidVideo + DealPriority int } // RequestData and ResponseData exist so that prebid-server core code can implement its "debug" functionality diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index b8b21b762d7..7c9651af747 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -550,8 +550,9 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro } prebid := openrtb_ext.ExtRequestPrebid{ - Cache: &cache, - Targeting: &targeting, + Cache: &cache, + Targeting: &targeting, + SupportDeals: videoRequest.SupportDeals, } extReq := openrtb_ext.ExtRequest{Prebid: prebid} diff --git a/exchange/bidder.go b/exchange/bidder.go index d9a28fee175..97f64e74bb5 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -51,11 +51,13 @@ type adaptedBidder interface { // pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange. // pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video. +// pbsOrtbBid.dealPriority will become "response.seatbid[i].bid.dealPriority" in the final OpenRTB response. type pbsOrtbBid struct { - bid *openrtb.Bid - bidType openrtb_ext.BidType - bidTargets map[string]string - bidVideo *openrtb_ext.ExtBidPrebidVideo + bid *openrtb.Bid + bidType openrtb_ext.BidType + bidTargets map[string]string + bidVideo *openrtb_ext.ExtBidPrebidVideo + dealPriority int } // pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder. @@ -183,9 +185,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * bidAdjustment * conversionRate } seatBid.bids = append(seatBid.bids, &pbsOrtbBid{ - bid: bidResponse.Bids[i].Bid, - bidType: bidResponse.Bids[i].BidType, - bidVideo: bidResponse.Bids[i].BidVideo, + bid: bidResponse.Bids[i].Bid, + bidType: bidResponse.Bids[i].BidType, + bidVideo: bidResponse.Bids[i].BidVideo, + dealPriority: bidResponse.Bids[i].DealPriority, }) } } else { diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 173bc37ee51..46f63cc66c4 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -39,13 +39,15 @@ func TestSingleBidder(t *testing.T) { Bid: &openrtb.Bid{ Price: firstInitialPrice, }, - BidType: openrtb_ext.BidTypeBanner, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, }, { Bid: &openrtb.Bid{ Price: secondInitialPrice, }, - BidType: openrtb_ext.BidTypeVideo, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, }, }, } @@ -88,6 +90,9 @@ func TestSingleBidder(t *testing.T) { if typedBid.BidType != seatBid.bids[index].bidType { t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) } + if typedBid.DealPriority != seatBid.bids[index].dealPriority { + t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + } } if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice { t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price) diff --git a/exchange/exchange.go b/exchange/exchange.go index 8c6cac4cfcd..ef10180a745 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -10,6 +10,7 @@ import ( "net/http" "runtime/debug" "sort" + "strings" "time" "github.com/prebid/prebid-server/stored_requests" @@ -167,12 +168,93 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } targData.setTargeting(auc, bidRequest.App != nil, bidCategory) } + + if requestExt.Prebid.SupportDeals { + dealErrs := applyDealSupport(bidRequest, auc) + errs = append(errs, dealErrs...) + } } // Build the response return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, errs) } +type DealTierInfo struct { + Prefix string `json:"prefix"` + MinDealTier int `json:"minDealTier"` +} + +type DealTier struct { + Info *DealTierInfo `json:"dealTier,omitempty"` +} + +type BidderDealTier struct { + DealInfo map[string]*DealTier +} + +// applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded +func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction) []error { + errs := []error{} + impDealMap := getDealTiers(bidRequest) + + for impID, topBidsPerImp := range auc.winningBidsByBidder { + impDeal := impDealMap[impID].DealInfo + for bidder, topBidPerBidder := range topBidsPerImp { + bidderString := bidder.String() + + if topBidPerBidder.dealPriority > 0 { + if validateAndNormalizeDealTier(impDeal[bidderString]) { + updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info) + } else { + errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", bidderString, impID)) + } + } + } + } + + return errs +} + +// getDealTiers creates map of impression to bidder deal tier configuration +func getDealTiers(bidRequest *openrtb.BidRequest) map[string]*BidderDealTier { + impDealMap := make(map[string]*BidderDealTier) + + for _, imp := range bidRequest.Imp { + var bidderDealTier BidderDealTier + err := json.Unmarshal(imp.Ext, &bidderDealTier.DealInfo) + if err != nil { + continue + } + + impDealMap[imp.ID] = &bidderDealTier + } + + return impDealMap +} + +func validateAndNormalizeDealTier(impDeal *DealTier) bool { + if impDeal == nil || impDeal.Info == nil { + return false + } + // Remove whitespace from prefix before checking if it can be used + impDeal.Info.Prefix = strings.ReplaceAll(impDeal.Info.Prefix, " ", "") + return len(impDeal.Info.Prefix) > 0 && impDeal.Info.MinDealTier > 0 +} + +func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo) { + if bid.dealPriority >= dealTierInfo.MinDealTier { + prefixTier := fmt.Sprintf("%s%d_", dealTierInfo.Prefix, bid.dealPriority) + + if oldCatDur, ok := bid.bidTargets["hb_pb_cat_dur"]; ok { + oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2) + oldCatDurSplit[0] = prefixTier + + newCatDur := strings.Join(oldCatDurSplit, "") + bid.bidTargets["hb_pb_cat_dur"] = newCatDur + } + } +} + func (e *exchange) makeAuctionContext(ctx context.Context, needsCache bool) (auctionCtx context.Context, cancel context.CancelFunc) { auctionCtx = ctx cancel = func() {} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7e199d4b750..0a64bce0826 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -914,10 +914,10 @@ func TestCategoryMapping(t *testing.T) { bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, 0} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -969,10 +969,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, 0} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, 0} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1023,9 +1023,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1105,9 +1105,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1156,10 +1156,10 @@ func TestCategoryDedupe(t *testing.T) { bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -1288,7 +1288,7 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*pbsOrtbBid{} for _, bid := range test.bids { currentBid := pbsOrtbBid{ - bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, + bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, 0, } innerBids = append(innerBids, ¤tBid) } @@ -1325,6 +1325,264 @@ func TestUpdateRejections(t *testing.T) { assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id2] reason: some reason 2", "Rejection message did not match expected") } +func TestApplyDealSupport(t *testing.T) { + testCases := []struct { + description string + dealPriority int + impExt json.RawMessage + targ map[string]string + expectedHbPbCatDur string + expectedDealErr string + }{ + { + description: "hb_pb_cat_dur should be modified", + dealPriority: 5, + impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + expectedHbPbCatDur: "tier5_movies_30s", + expectedDealErr: "", + }, + { + description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", + dealPriority: 9, + impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + expectedHbPbCatDur: "12.00_medicine_30s", + expectedDealErr: "", + }, + { + description: "hb_pb_cat_dur should not be modified due to invalid config", + dealPriority: 5, + impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_games_30s", + }, + expectedHbPbCatDur: "12.00_games_30s", + expectedDealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", + }, + { + description: "hb_pb_cat_dur should not be modified due to deal priority of 0", + dealPriority: 0, + impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_auto_30s", + }, + expectedHbPbCatDur: "12.00_auto_30s", + expectedDealErr: "", + }, + } + + bidderName := openrtb_ext.BidderName("appnexus") + for _, test := range testCases { + bidRequest := &openrtb.BidRequest{ + ID: "some-request-id", + Imp: []openrtb.Imp{ + { + ID: "imp_id1", + Ext: test.impExt, + }, + }, + } + + bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + + auc := &auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "imp_id1": { + bidderName: &bid, + }, + }, + } + + dealErrs := applyDealSupport(bidRequest, auc) + + assert.Equal(t, test.expectedHbPbCatDur, auc.winningBidsByBidder["imp_id1"][bidderName].bidTargets["hb_pb_cat_dur"], test.description) + if len(test.expectedDealErr) > 0 { + assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors") + } + } +} + +func TestGetDealTiers(t *testing.T) { + testCases := []struct { + impExt json.RawMessage + bidderResult map[string]bool // true indicates bidder had valid config, false indicates invalid + }{ + { + impExt: json.RawMessage(`{"validbase": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + bidderResult: map[string]bool{ + "validbase": true, + }, + }, + { + impExt: json.RawMessage(`{"validmultiple1": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}, "validmultiple2": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + bidderResult: map[string]bool{ + "validmultiple1": true, + "validmultiple2": true, + }, + }, + { + impExt: json.RawMessage(`{"nodealtier": {"placementId": 10433394}}`), + bidderResult: map[string]bool{ + "nodealtier": false, + }, + }, + { + impExt: json.RawMessage(`{"validbase": {"placementId": 10433394}, "onedealTier2": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + bidderResult: map[string]bool{ + "onedealTier2": true, + "validbase": false, + }, + }, + } + + filledDealTier := DealTier{ + Info: &DealTierInfo{ + Prefix: "tier", + MinDealTier: 5, + }, + } + emptyDealTier := DealTier{} + + for _, test := range testCases { + bidRequest := &openrtb.BidRequest{ + ID: "some-request-id", + Imp: []openrtb.Imp{ + { + ID: "imp_id1", + Ext: test.impExt, + }, + }, + } + + impDealMap := getDealTiers(bidRequest) + + for bidder, valid := range test.bidderResult { + if valid { + assert.Equal(t, &filledDealTier, impDealMap["imp_id1"].DealInfo[bidder], "DealTier should be filled with config data") + } else { + assert.Equal(t, &emptyDealTier, impDealMap["imp_id1"].DealInfo[bidder], "DealTier should be empty") + } + } + } +} + +func TestValidateAndNormalizeDealTier(t *testing.T) { + testCases := []struct { + description string + params json.RawMessage + expectedResult bool + }{ + { + description: "BidderDealTier should be valid", + params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`), + expectedResult: true, + }, + { + description: "BidderDealTier should be invalid due to empty prefix", + params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}`), + expectedResult: false, + }, + { + description: "BidderDealTier should be invalid due to empty dealTier", + params: json.RawMessage(`{"appnexus": {"dealTier": {}, "placementId": 10433394}}`), + expectedResult: false, + }, + { + description: "BidderDealTier should be invalid due to missing minDealTier", + params: json.RawMessage(`{"appnexus": {"dealTier": {"prefix": "tier"}, "placementId": 10433394}}`), + expectedResult: false, + }, + { + description: "BidderDealTier should be invalid due to missing dealTier", + params: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), + expectedResult: false, + }, + { + description: "BidderDealTier should be invalid due to prefix containing all whitespace", + params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": " "}, "placementId": 10433394}}`), + expectedResult: false, + }, + { + description: "BidderDealTier should be valid after removing whitespace", + params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": " prefixwith sp aces "}, "placementId": 10433394}}`), + expectedResult: true, + }, + } + + for _, test := range testCases { + var bidderDealTier BidderDealTier + err := json.Unmarshal(test.params, &bidderDealTier.DealInfo) + if err != nil { + assert.Fail(t, "Unable to unmarshal JSON data for testing BidderDealTier") + } + + assert.Equal(t, test.expectedResult, validateAndNormalizeDealTier(bidderDealTier.DealInfo["appnexus"]), test.description) + } +} + +func TestUpdateHbPbCatDur(t *testing.T) { + testCases := []struct { + description string + targ map[string]string + dealTier *DealTierInfo + dealPriority int + expectedHbPbCatDur string + }{ + { + description: "hb_pb_cat_dur should be updated with prefix and tier", + targ: map[string]string{ + "hb_pb": "12.00", + "hb_pb_cat_dur": "12.00_movies_30s", + }, + dealTier: &DealTierInfo{ + Prefix: "tier", + MinDealTier: 5, + }, + dealPriority: 5, + expectedHbPbCatDur: "tier5_movies_30s", + }, + { + description: "hb_pb_cat_dur should not be updated due to bid priority", + targ: map[string]string{ + "hb_pb": "12.00", + "hb_pb_cat_dur": "12.00_auto_30s", + }, + dealTier: &DealTierInfo{ + Prefix: "tier", + MinDealTier: 10, + }, + dealPriority: 6, + expectedHbPbCatDur: "12.00_auto_30s", + }, + { + description: "hb_pb_cat_dur should be updated with prefix and tier", + targ: map[string]string{ + "hb_pb": "12.00", + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + dealTier: &DealTierInfo{ + Prefix: "tier", + MinDealTier: 1, + }, + dealPriority: 7, + expectedHbPbCatDur: "tier7_medicine_30s", + }, + } + + for _, test := range testCases { + bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + + updateHbPbCatDur(&bid, test.dealTier) + + assert.Equal(t, test.expectedHbPbCatDur, bid.bidTargets["hb_pb_cat_dur"], test.description) + } +} + type exchangeSpec struct { IncomingRequest exchangeRequest `json:"incomingRequest"` OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index 454476857a4..f7ddf203294 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -136,6 +136,14 @@ type BidRequestVideo struct { // Description: // Contains the OpenRTB Regs object to be passed to OpenRTB request Regs *openrtb.Regs `json:"regs,omitempty"` + + // Attribute: + // supportdeals + // Type: + // bool; optional + // Description: + // Indicates that the response should update key to include prefix and tier + SupportDeals bool `json:"supportdeals,omitempty"` } type PodConfig struct { diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 9226ff294d5..ee1a0cd0f8b 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -17,6 +17,7 @@ type ExtRequestPrebid struct { Cache *ExtRequestPrebidCache `json:"cache,omitempty"` StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + SupportDeals bool `json:"supportdeals,omitempty"` } // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache From 7be0a4d68832679d71a0a11f1ff01420866d40b9 Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Fri, 28 Feb 2020 00:25:22 +0530 Subject: [PATCH 018/603] updating default hard-coded list of certs (#1201) Co-authored-by: Shalmali Patil --- ssl/ssl.go | 3230 ++++++++++++++++++++-------------------------------- 1 file changed, 1206 insertions(+), 2024 deletions(-) diff --git a/ssl/ssl.go b/ssl/ssl.go index d05c90154b9..a424cd9f54b 100644 --- a/ssl/ssl.go +++ b/ssl/ssl.go @@ -40,29 +40,6 @@ func AppendPEMFileToRootCAPool(certPool *x509.CertPool, pemFileName string) (*x5 var pemCerts = []byte(` -----BEGIN CERTIFICATE----- -MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB -VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp -bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R -dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw -MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy -dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52 -ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM -EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj -lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ -znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH -2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1 -k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs -2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD -VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG -KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+ -8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R -FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS -mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE -DNuxUCAKGkq6ahq97BvIxYSazQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ @@ -107,74 +84,36 @@ d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE -AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x -CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW -MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF -RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7 -09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7 -XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P -Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK -t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb -X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28 -MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU -fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI -2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH -K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae -ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP -BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ -MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw -RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv -bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm -fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3 -gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe -I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i -5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi -ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn -MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ -o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6 -zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN -GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt -r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK -Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx -CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp -ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa -QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw -NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft -ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu -QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG -qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL -fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ -Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 -Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ -54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b -MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j -ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej -YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt -A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF -rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ -pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB -lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy -YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 -7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs -YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 -xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc -unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ -Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp -ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 -gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 -jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ -XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD -W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ -RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r -MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk -BYn8eNZcLCZDqQ== +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE @@ -235,79 +174,6 @@ c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw -MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD -VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul -CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n -tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl -dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch -PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC -+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O -BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk -ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X -7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz -43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY -eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl -pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA -WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx -MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB -ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV -BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV -6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX -GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP -dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH -1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF -62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW -BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL -MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU -cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv -b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 -IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ -iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao -GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh -4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm -XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 -MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK -EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh -BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq -xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G -87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i -2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U -WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 -0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G -A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr -pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL -ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm -aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv -hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm -hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X -dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 -P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y -iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no -xqE= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL @@ -392,81 +258,80 @@ aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc -MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp -b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT -AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs -aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H -j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K -f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55 -IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw -FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht -QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm -/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ -k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ -MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC -seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ -hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+ -eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U -DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj -B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL -rosot4LKGAfmt1t06SAZf7IbiVQ= +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE @@ -546,26 +411,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0 -ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX -l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB -HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B -5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3 -WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP -gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+ -DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu -BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs -h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk -LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow @@ -597,26 +442,6 @@ I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg -isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z -NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI -+MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R -hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+ -mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP -Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s -EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2 -mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC -e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow -dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow @@ -648,61 +473,6 @@ u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq 4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET -MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE -AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw -CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg -YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE -Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX -mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD -XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW -S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp -FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD -AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu -ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z -ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv -Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw -DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6 -yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq -EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ -CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB -EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN -PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy -MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk -D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o -OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A -fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe -IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n -oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK -/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj -rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD -3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE -7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC -yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd -qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI -hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR -xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA -SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo -HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB -emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC -AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb -7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x -DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk -F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF -a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT -Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy @@ -734,24 +504,36 @@ zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD -TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2 -MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF -Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh -IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6 -dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO -V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC -GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN -v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB -AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB -Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO -76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK -OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH -ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi -yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL -buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj -2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE= +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB @@ -795,60 +577,38 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn -MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL -ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg -b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa -MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB -ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw -IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B -AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb -unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d -BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq -7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 -0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX -roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG -A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j -aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p -26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA -BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud -EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN -BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz -aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB -AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd -p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi -1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc -XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 -eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu -tGWaIZDgqtCYvDi1czyL+Nw= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn -MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL -ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo -YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 -MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy -NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G -A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA -A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 -Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s -QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV -eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 -B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh -z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T -AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i -ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w -TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH -MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD -VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE -VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh -bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B -AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM -bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi -ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG -VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c -ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ -AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV @@ -873,36 +633,36 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk -BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4 -Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl -cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0 -aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY -F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N -8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe -rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K -/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu -7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC -28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6 -lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E -nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB -0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09 -5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj -WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN -jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ -KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s -ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM -OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q -619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn -2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj -o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v -nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG -5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq -pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb -dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0 -BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5 +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw @@ -927,23 +687,49 @@ kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 l7+ijrRU -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM -MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD -QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM -MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD -QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E -jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo -ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI -ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu -Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg -AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 -HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA -uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa -TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg -xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q -CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x -O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs -6GAqm4VKQPNriiTsBhYscw== +MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a +iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt +6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP +0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f +6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE +EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN +1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc +h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT +mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV +4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO +WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd +Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq +hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh +66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7 +/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS +S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j +2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R +Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr +RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy +6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV +V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5 +g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl +++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat +93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x +Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj +FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG +SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch +p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal +U5ORGpOucGpnutee5WEaXw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM @@ -968,6 +754,40 @@ VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 @@ -1010,74 +830,6 @@ OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ d0jQ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC -Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g -Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0 -aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa -Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg -SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo -aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp -ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z -7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA// -DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx -zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8 -hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs -4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u -gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY -NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E -FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3 -j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG -52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB -echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws -ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI -zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy -wy39FCqQmbkHzJ8= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0 -MRMwEQYDVQQDEwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQG -EwJJTDAeFw0wNDAzMjQxMTMyMThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMT -CkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNpZ24xCzAJBgNVBAYTAklMMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49qROR+WCf4C9DklBKK -8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTyP2Q2 -98CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb -2CEJKHxNGGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxC -ejVb7Us6eva1jsz/D3zkYDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7Kpi -Xd3DTKaCQeQzC6zJMw9kglcq/QytNuEMrkvF7zuZ2SOzW120V+x0cAwqTwIDAQAB -o4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2Zl -ZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0PAQH/BAQD -AgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRL -AZs+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWd -foPPbrxHbvUanlR2QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0M -cXS6hMTXcpuEfDhOZAYnKuGntewImbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq -8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb/627HOkthIDYIb6FUtnUdLlp -hbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VGzT2ouvDzuFYk -Res3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U -AGegcQCCSA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw -PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu -MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx -GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL -MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf -HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh -gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW -v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue -Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr -9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt -6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7 -MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl -Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58 -ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq -hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p -iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC -dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL -kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL -hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz -OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj @@ -1103,56 +855,6 @@ l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp -ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow -fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV -BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM -cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S -HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 -CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk -3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz -6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV -HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv -Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw -Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww -DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 -5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj -Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI -gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ -aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl -izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 -aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla -MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD -VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW -fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt -TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL -fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW -1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 -kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G -A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v -ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo -dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu -Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ -HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS -jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ -xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn -dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE @@ -1225,30 +927,6 @@ xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx -ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w -MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD -VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx -FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu -ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7 -gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH -fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a -ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT -ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk -c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto -dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt -aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI -hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk -QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/ -h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq -nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR -rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 -9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow @@ -1313,6 +991,43 @@ H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD @@ -1335,6 +1050,43 @@ YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j @@ -1358,64 +1110,36 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV -UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL -EwhEU1RDQSBFMTAeFw05ODEyMTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJ -BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x -ETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCg -bIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJENySZ -j9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlV -Sn5JTe2io74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCG -SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx -JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI -RFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMTAxODEw -MjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFGp5 -fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i -+DAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG -SIb3DQEBBQUAA4GBACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lN -QseSJqBcNJo4cvj9axY+IO6CizEqkzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+ -gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4RbyhkwS7hp86W0N6w4pl ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV -UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL -EwhEU1RDQSBFMjAeFw05ODEyMDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJ -BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x -ETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC/ -k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGODVvso -LeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3o -TQPMx7JSxhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCG -SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx -JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI -RFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMDkxOTE3 -MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFB6C -TShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5 -WzAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG -SIb3DQEBBQUAA4GBAEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHR -xdf0CiUPPXiBng+xZ8SQTGPdXqfiup/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVL -B3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1mPnHfxsb1gYgAlihw6ID ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp -Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp -a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx -MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg -R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg -U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU -MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT -L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H -5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC -90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1 -c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE -VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP -qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S -/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj -/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X -KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV @@ -1454,40 +1178,6 @@ y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV -BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt -ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4 -MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg -SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl -a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h -4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk -tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s -tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL -dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4 -c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um -TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z -+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O -Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW -OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW -fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2 -l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB -/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw -FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+ -8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI -6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO -TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME -wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY -Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn -xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q -DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q -Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t -hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4 -7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7 -QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB 8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 @@ -1568,34 +1258,6 @@ bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er fF6adulZkMV8gzURZVE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW @@ -1623,70 +1285,79 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy -dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 -MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx -dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f -BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A -cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm -aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw -ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj -IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y -7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh -1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT @@ -1709,27 +1380,6 @@ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx @@ -1854,6 +1504,33 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw @@ -2006,6 +1683,23 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p @@ -2031,6 +1725,41 @@ Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI l7WdmplNsDz4SgCbZN2fOUvRJ9e4 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG @@ -2051,28 +1780,97 @@ fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi AmvZWg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT -AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ -TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG -9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw -MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM -BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO -MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 -LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI -s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 -xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 -u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b -F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx -Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd -PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV -HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx -NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF -AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ -L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY -YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg -Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a -NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R -0982gaEbeC9xs/FZTEYYKKuF0mBWWg== +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 @@ -2109,76 +1907,37 @@ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN -AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp -dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw -MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw -CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ -MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB -SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz -ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH -LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP -PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL -2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w -ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC -MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk -AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0 -AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz -AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz -AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f -BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE -FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY -P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi -CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g -kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95 -HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS -na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q -qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z -TbvGRNs2yyqcjg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw -cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy -b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z -ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4 -NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN -TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p -Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u -uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+ -LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA -vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770 -Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx -62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB -AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw -LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP -BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB -AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov -MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5 -ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn -AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT -AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh -ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo -AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa -AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln -bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p -Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP -PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv -Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB -EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu -w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj -cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV -HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI -VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS -BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS -b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS -8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds -ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl -7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a -86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR -hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/ -MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV +BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw +MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B +LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F +ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem +hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 +EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn +Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 +zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ +96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m +j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g +DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ +8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j +X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH +hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB +KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 +Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT ++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL +BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 +BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO +jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 +loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c +qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ +2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ +JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre +zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf +LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ +x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 +oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD @@ -2229,144 +1988,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD -EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 -OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l -dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG -SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK -gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX -iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc -Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E -BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G -SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu -b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh -bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv -Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln -aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 -IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph -biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo -ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP -UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj -YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA -bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 -sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa -n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS -NitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD -EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X -DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw -DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u -c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr -TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA -OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC -2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW -RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P -AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW -ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 -YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz -b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO -ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB -IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs -b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s -YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg -a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g -SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 -aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg -YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg -Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY -ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g -pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 -Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV -MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe -TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0 -dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB -KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0 -N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC -dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu -MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL -b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD -zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi -3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8 -WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY -Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi -NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC -ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4 -QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0 -YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz -aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu -IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm -ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg -ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs -amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv -IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3 -Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6 -ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1 -YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg -dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs -b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G -CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO -xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP -0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ -QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk -f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK -8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQD -EzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVneXpvaSAoQ2xhc3MgUUEpIFRhbnVz -aXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0bG9jay5odTAeFw0w -MzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5l -dExvY2sgTWlub3NpdGV0dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZh -bnlraWFkbzEeMBwGCSqGSIb3DQEJARYPaW5mb0BuZXRsb2NrLmh1MIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRVCacbvWy5FPSKAtt2/Goq -eKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e8ia6AFQe -r7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO5 -3Lhbm+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWd -vLrqOU+L73Sa58XQ0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0l -mT+1fMptsK6ZmfoIYOcZwvK9UdPM0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4IC -wDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8EBAMCAQYwggJ1Bglg -hkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2YW55IGEgTmV0 -TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh -biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQg -ZWxla3Ryb25pa3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywg -dmFsYW1pbnQgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6 -b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwgYXogQWx0YWxhbm9zIFN6ZXJ6b2Rl -c2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kgZWxqYXJhcyBtZWd0 -ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczovL3d3 -dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0Bu -ZXRsb2NrLm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBh -bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRo -ZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMgYXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3 -Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0IGluZm9AbmV0bG9jay5u -ZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3DQEBBQUA -A4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQ -MznNwNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+ -NFAwLvt/MpqNPfMgW/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCR -VCHnpgu0mfVRQdzNo0ci2ccBgcTcR08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY -83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR5qq5aKrN9p2QdRLqOBrKROi3 -macqaJVmlaut74nLYKkGEsaUR+ko ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp @@ -2414,57 +2035,104 @@ Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ /L7fCg0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s -YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz -dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0 -aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh -IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ -KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw -MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy -b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx -KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG -A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u -aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9 -7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74 -BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G -ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9 -JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0 -PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2 -0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH -0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/ -6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m -v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7 -K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev -bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw -MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w -MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD -gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0 -b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh -bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0 -cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp -ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg -ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq -hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD -AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w -MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag -RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t -UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl -cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v -Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG -AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN -AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS -1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB -3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv -Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh -HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm -pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz -sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE -qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb -mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9 -opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H -YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b +wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX +/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0 +77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP +uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx +p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx +Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2 +TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W +G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw +vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY +EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1 +2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw +DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E +PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf +gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS +FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0 +V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P +XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I +i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t +TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91 +09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky +Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ +AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj +1oxx +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh +/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e +CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6 +1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE +FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS +gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X +G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy +YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH +vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4 +t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/ +gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3 +5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w +DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz +Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0 +nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT +RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT +wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2 +t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa +TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2 +o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU +3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA +iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f +WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM +S1IK +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx +CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U +cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow +QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl +blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm +3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d +oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5 +DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK +BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q +j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx +4nxp5V2a+EEfOzmTk51V6s2N8fvB -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC @@ -2501,6 +2169,37 @@ xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK SnQ2+Q== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV @@ -2534,6 +2233,37 @@ ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y 8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV @@ -2572,141 +2302,156 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6 -MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp -dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX -BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy -MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp -eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg -/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl -wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh -AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2 -PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu -AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR -MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc -HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/ -Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+ -f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO -rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch -6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3 -7CAFYd4= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF -UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ -R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN -MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G -A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw -JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+ -WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj -SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl -u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy -A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk -Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7 -MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr -aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC -IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A -cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA -YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA -bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA -bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA -aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA -aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA -ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA -YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA -ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA -LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6 -Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y -eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw -CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G -A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu -Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn -lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt -b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg -9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF -ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC -IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCB -rjELMAkGA1UEBhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcp -MRIwEAYDVQQHEwlTdHV0dGdhcnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fz -c2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVTLVRSVVNUIEF1dGhlbnRpY2F0aW9u -IGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0wNTA2MjIwMDAwMDBa -Fw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFkZW4t -V3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMg -RGV1dHNjaGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJV -U1QgQXV0aGVudGljYXRpb24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBO -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1 -toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob4QSwI7+Vio5bG0F/WsPo -TUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXLg3KSwlOy -ggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1 -XgqfeN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteF -hy+S8dF2g08LOlk3KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm -7QIDAQABo4GSMIGPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG -MCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJvbmxpbmUxLTIwNDgtNTAdBgNV -HQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAUD8oeXHngovMp -ttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD -pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFo -LtU96G7m1R08P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersF -iXOMy6ZNwPv2AtawB6MDwidAnwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0y -h9WUUpY6RsZxlj33mA6ykaqP2vROJAA5VeitF7nTNCtKqUDMFypVZUF0Qn71wK/I -k63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8bHz2eBIPdltkdOpQ= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIGGTCCBAGgAwIBAgIIPtVRGeZNzn4wDQYJKoZIhvcNAQELBQAwajEhMB8GA1UE -AxMYU0cgVFJVU1QgU0VSVklDRVMgUkFDSU5FMRwwGgYDVQQLExMwMDAyIDQzNTI1 -Mjg5NTAwMDIyMRowGAYDVQQKExFTRyBUUlVTVCBTRVJWSUNFUzELMAkGA1UEBhMC -RlIwHhcNMTAwOTA2MTI1MzQyWhcNMzAwOTA1MTI1MzQyWjBqMSEwHwYDVQQDExhT -RyBUUlVTVCBTRVJWSUNFUyBSQUNJTkUxHDAaBgNVBAsTEzAwMDIgNDM1MjUyODk1 -MDAwMjIxGjAYBgNVBAoTEVNHIFRSVVNUIFNFUlZJQ0VTMQswCQYDVQQGEwJGUjCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANqoVgLsfJXwTukK0rcHoyKL -ULO5Lhk9V9sZqtIr5M5C4myh5F0lHjMdtkXRtPpZilZwyW0IdmlwmubHnAgwE/7m -0ZJoYT5MEfJu8rF7V1ZLCb3cD9lxDOiaN94iEByZXtaxFwfTpDktwhpz/cpLKQfC -eSnIyCauLMT8I8hL4oZWDyj9tocbaF85ZEX9aINsdSQePHWZYfrSFPipS7HYfad4 -0hNiZbXWvn5qA7y1svxkMMPQwpk9maTTzdGxxFOHe0wTE2Z/v9VlU2j5XB7ltP82 -mUWjn2LAfxGCAVTeD2WlOa6dSEyJoxA74OaD9bDaLB56HFwfAKzMq6dgZLPGxXvH -VUZ0PJCBDkqOWZ1UsEixUkw7mO6r2jS3U81J2i/rlb4MVxH2lkwEeVyZ1eXkvm/q -R+5RS+8iJq612BGqQ7t4vwt+tN3PdB0lqYljseI0gcSINTjiAg0PE8nVKoIV8IrE -QzJW5FMdHay2z32bll0eZOl0c8RW5BZKUm2SOdPhTQ4/YrnerbUdZbldUv5dCamc -tKQM2S9FdqXPjmqanqqwEaHrYcbrPx78ZrQSnUZ/MhaJvnFFr5Eh2f2Tv7QCkUL/ -SR/tixVo3R+OrJvdggWcRGkWZBdWX0EPSk8ED2VQhpOX7EW/XcIc3M/E2DrmeAXQ -xVVVqV7+qzohu+VyFPcLAgMBAAGjgcIwgb8wHQYDVR0OBBYEFCkgy/HDD9oGjhOT -h/5fYBopu/O2MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUKSDL8cMP2gaO -E5OH/l9gGim787YwEQYDVR0gBAowCDAGBgRVHSAAMEkGA1UdHwRCMEAwPqA8oDqG -OGh0dHA6Ly9jcmwuc2d0cnVzdHNlcnZpY2VzLmNvbS9yYWNpbmUtR3JvdXBlU0cv -TGF0ZXN0Q1JMMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEATEZn -4ERQ9cW2urJRCiUTHbfHiC4fuStkoMuTiFJZqmD1zClSF/8E5ze0MRFGfisebKeL -PEeaXvSqXZA7RT2fSsmKe47A7j55i5KjyJRKuCgRa6YlX129x8j7g09VMeZc8BN8 -471/Kiw3N5RJr4QfFCeiWBCPCjk3GhIgQY8Z9qkfGe2yNLKtfTNEi18KB0PydkVF -La3kjQ4A/QQIqudr+xe9sAhWDjUqcvCz5006Tw3c82ASszhkjNv54SaNL+9O6CRH -PjY0imkPKGuLh8a9hSb50+tpIVZgkdb34GLCqHGuLt5mI7VSRqakSDcsfwEWVxH3 -Jw0O5Q/WkEXhHj8h3NL8FhgTPk1qsiZqQF4leP049KxYejcbmEAEx47J1MRnYbGY -rvDNDty5r2WDewoEij9hqvddQYbmxkzCTzpcVuooO6dEz8hKZPVyYC3jQ7hK4HU8 -MuSqFtcRucFF2ZtmY2blIrc07rrVdC8lZPOBVMt33lfUk+OsBzE6PlwDg1dTx/D+ -aNglUE0SyObhlY1nqzyTPxcCujjXnvcwpT09RAEzGpqfjtCf8e4wiHPvriQZupdz -FcHscQyEZLV77LxpPqRtCRY2yko5isune8YdfucziMm+MG2chZUh6Uc7Bn6B4upG -5nBYgOao8p0LadEziVkw82TTC/bOKwn7fRB2LhA= +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr @@ -2774,27 +2519,6 @@ iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz -MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N -IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11 -bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE -RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO -zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5 -bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF -MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1 -VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC -OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G -CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW -tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ -q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb -EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+ -Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O -VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX @@ -2836,25 +2560,6 @@ JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx -MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG -29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk -oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk -3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL -qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN -nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX -ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H -DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO -TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv -kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w -zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV @@ -2874,26 +2579,36 @@ Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO -TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy -MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk -ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn -ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71 -9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO -hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U -tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o -BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh -SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww -OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv -cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA -7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k -/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm -eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6 -u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy -7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR -iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO @@ -2929,6 +2644,38 @@ Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw @@ -3000,80 +2747,6 @@ iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn sSi6 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul -F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC -ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w -ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk -aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 -YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg -c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 -d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG -CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 -dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF -wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS -Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst -0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc -pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl -CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF -P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK -1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm -KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE -JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ -8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm -fyWl8kgAwKQB2j8= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 -OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG -A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ -JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD -vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo -D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ -Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW -RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK -HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN -nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM -0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i -UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 -Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg -TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL -BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K -2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX -UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl -6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK -9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ -HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI -wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY -XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l -IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo -hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr -so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF @@ -3107,39 +2780,6 @@ ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE -BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu -IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw -WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD -ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y -IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn -IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+ -6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob -jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw -izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl -+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY -zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP -pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF -KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW -ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB -AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O -BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0 -ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW -IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA -A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0 -uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+ -FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7 -jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/ -u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D -YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1 -puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa -icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG -DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x -kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z -Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow @@ -3173,108 +2813,6 @@ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk -MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 -YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg -Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT -AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp -Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9 -m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih -FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/ -TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F -EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco -kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu -HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF -vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo -19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC -L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW -bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX -JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw -FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j -BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc -K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf -ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik -Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB -sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e -3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR -ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip -mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH -b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf -rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms -hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y -zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6 -MBr1mmz0DlP5OlvRHA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk -MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 -YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg -Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT -AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp -Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr -jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r -0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f -2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP -ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF -y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA -tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL -6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0 -uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL -acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh -k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q -VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw -FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O -BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh -b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R -fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv -/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI -REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx -srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv -aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT -woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n -Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W -t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N -8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2 -9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5 -wSsSnqaeG8XmDtkx2Q== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw -ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp -dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290 -IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD -VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy -dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg -MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx -UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD -1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH -oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR -HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/ -5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv -idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL -OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC -NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f -46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB -UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth -7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G -A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED -MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB -bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x -XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T -PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0 -Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70 -WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL -Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm -7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S -nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN -vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB -WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI -fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb -I+2ksx0WckNLIOFZfsLorSa/ovc= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl @@ -3321,180 +2859,30 @@ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE -SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg -Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV -BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl -cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA -vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu -Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a -0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 -4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN -eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD -R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG -A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu -dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME -Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 -WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw -HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ -KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO -Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX -wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 -9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 -jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 -aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg -MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 -dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz -MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy -dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD -VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg -xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu -xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7 -XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k -heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J -YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C -urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1 -JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51 -b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV -9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7 -kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh -fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy -B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA -aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS -RGQDJereW26fyfJOrN3H ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS -S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg -SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3 -WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv -bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU -UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw -bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe -LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef -J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh -R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ -Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX -JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p -zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S -Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ -KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq -ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 -Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz -gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH -uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS -y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS -S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg -SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx -OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry -b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC -VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE -sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F -ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY -KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG -+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG -HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P -IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M -733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk -Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G -CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW -AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I -aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5 -mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa -XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ -qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9 +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx @@ -3611,42 +2999,90 @@ HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF @@ -3670,149 +3106,79 @@ jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN ZetX2fNXlrtIzYE= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS -MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp -bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw -VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy -YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy -dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2 -ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe -Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx -GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls -aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU -QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh -xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0 -aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr -IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h -gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK -O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO -fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw -lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL -hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID -AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP -NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t -wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM -7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh -gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n -oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs -yZyQ2uypQjyttgI= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB -rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt -Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa -Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV -BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l -dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE -AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B -YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9 -hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l -L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm -SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM -1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws -6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw -Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50 -aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH -AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u -7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0 -xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ -rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim -eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk -USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB -lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt -SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG -A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe -MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v -d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh -cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn -0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ -M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a -MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd -oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI -DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy -oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD -VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 -dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy -bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF -BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM -//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli -CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE -CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t -3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS -KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE +BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn +aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg +QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0 +MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD +VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 +dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom +/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR +Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3 +4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z +5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0 +hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID +AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX +SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l +VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf +peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF +Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW ++qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL @@ -3892,139 +3258,6 @@ lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 7M2CYfE45k+XmCpajQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f -zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi -TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBAFgVKTk8d6PaXCUDfGD67gmZPCcQcMgMCeazh88K4hiW -NWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n0a3hUKw8fGJLj7qE1xIV -Gx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZRjXZ+Hxb ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK -VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm -Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J -h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul -uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68 -DzFc6PLZ ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4 -nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO -8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV -ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb -PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2 -6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr -n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a -qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4 -wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 -ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs -pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4 -E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns -YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y -aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe -Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj -IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx -KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM -HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw -DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC -AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji -nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX -rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn -jBJ7xUS0rg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy -aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s -IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp -Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV -BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp -Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu -Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g -Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt -IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU -J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO -JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY -wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o -koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN -qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E -Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe -xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u -7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU -sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI -sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP -cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu @@ -4049,30 +3282,6 @@ F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 -GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ -+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd -U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm -NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY -ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ -ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 -CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq -g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c -2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ -bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv @@ -4095,34 +3304,6 @@ LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd 398znM/jra6O1I7mT1GvFpLgXPYHDw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx -IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs -cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v -dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0 -MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl -bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD -DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r -WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU -Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs -HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj -z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf -SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl -AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG -KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P -AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j -BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC -VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX -ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg -Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB -ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd -/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB -A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn -k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9 -iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv -2G0xffX8oRAHh84vWdw+WNs= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY @@ -4265,4 +3446,5 @@ t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE-----`) +-----END CERTIFICATE----- +`) From 6a26430d7e6516cd5e3c80bfa89f8738a8211dd8 Mon Sep 17 00:00:00 2001 From: DmitryStashkevich <34479135+DmitryStashkevich@users.noreply.github.com> Date: Thu, 27 Feb 2020 20:55:57 +0200 Subject: [PATCH 019/603] add admixer adapter (#1195) --- adapters/admixer/admixer.go | 184 ++++++++++++++++++ adapters/admixer/admixer_test.go | 10 + .../exemplary/optional-params.json | 104 ++++++++++ .../exemplary/simple-app-audio.json | 89 +++++++++ .../exemplary/simple-app-banner.json | 101 ++++++++++ .../exemplary/simple-app-native.json | 90 +++++++++ .../exemplary/simple-app-video.json | 111 +++++++++++ .../exemplary/simple-site-audio.json | 89 +++++++++ .../exemplary/simple-site-banner.json | 101 ++++++++++ .../exemplary/simple-site-native.json | 90 +++++++++ .../exemplary/simple-site-video.json | 111 +++++++++++ .../admixertest/params/race/audio.json | 5 + .../admixertest/params/race/banner.json | 5 + .../admixertest/params/race/native.json | 5 + .../admixertest/params/race/video.json | 5 + .../supplemental/bad-dsp-request-example.json | 70 +++++++ .../dsp-server-internal-error-example.json | 70 +++++++ .../supplemental/ext-unmarshall-error.json | 34 ++++ .../unknown-status-code-example.json | 70 +++++++ .../supplemental/wrong-zone-id-error.json | 30 +++ .../supplemental/zero-bid-request-error.json | 19 ++ adapters/admixer/params_test.go | 57 ++++++ adapters/admixer/usersync.go | 11 ++ adapters/admixer/usersync_test.go | 34 ++++ config/config.go | 2 + exchange/adapter_map.go | 2 + go.mod | 15 +- go.sum | 60 +++++- openrtb_ext/bidders.go | 2 + openrtb_ext/imp_admixer.go | 7 + static/bidder-info/admixer.yaml | 15 ++ static/bidder-params/admixer.json | 25 +++ usersync/usersyncers/syncer.go | 5 +- usersync/usersyncers/syncer_test.go | 1 + 34 files changed, 1623 insertions(+), 6 deletions(-) create mode 100644 adapters/admixer/admixer.go create mode 100644 adapters/admixer/admixer_test.go create mode 100644 adapters/admixer/admixertest/exemplary/optional-params.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-app-audio.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-app-banner.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-app-native.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-app-video.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-site-audio.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-site-banner.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-site-native.json create mode 100644 adapters/admixer/admixertest/exemplary/simple-site-video.json create mode 100644 adapters/admixer/admixertest/params/race/audio.json create mode 100644 adapters/admixer/admixertest/params/race/banner.json create mode 100644 adapters/admixer/admixertest/params/race/native.json create mode 100644 adapters/admixer/admixertest/params/race/video.json create mode 100644 adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json create mode 100644 adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json create mode 100644 adapters/admixer/admixertest/supplemental/ext-unmarshall-error.json create mode 100644 adapters/admixer/admixertest/supplemental/unknown-status-code-example.json create mode 100644 adapters/admixer/admixertest/supplemental/wrong-zone-id-error.json create mode 100644 adapters/admixer/admixertest/supplemental/zero-bid-request-error.json create mode 100644 adapters/admixer/params_test.go create mode 100644 adapters/admixer/usersync.go create mode 100644 adapters/admixer/usersync_test.go create mode 100644 openrtb_ext/imp_admixer.go create mode 100644 static/bidder-info/admixer.yaml create mode 100644 static/bidder-params/admixer.json diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go new file mode 100644 index 00000000000..65a94f352ed --- /dev/null +++ b/adapters/admixer/admixer.go @@ -0,0 +1,184 @@ +package admixer + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type AdmixerAdapter struct { + endpoint string +} + +func NewAdmixerBidder(endpoint string) *AdmixerAdapter { + return &AdmixerAdapter{endpoint: endpoint} +} + +type admixerImpExt struct { + CustomParams map[string]interface{} `json:"customParams"` +} + +func (a *AdmixerAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { + rq, errs := a.makeRequest(request) + + if len(errs) > 0 { + errors = append(errors, errs...) + return + } + + if rq != nil { + requests = append(requests, rq) + } + + return +} + +func (a *AdmixerAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { + var errs []error + var validImps []openrtb.Imp + + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impressions in request", + }} + } + + for _, imp := range request.Imp { + if err := preprocess(&imp); err != nil { + errs = append(errs, err) + continue + } + validImps = append(validImps, imp) + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, errs +} + +func preprocess(imp *openrtb.Imp) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + var admixerExt openrtb_ext.ExtImpAdmixer + if err := json.Unmarshal(bidderExt.Bidder, &admixerExt); err != nil { + return &errortypes.BadInput{ + Message: "Wrong Admixer bidder ext", + } + } + + //don't use regexp due to possible performance reduce + if len(admixerExt.ZoneId) != 36 { + return &errortypes.BadInput{ + Message: "ZoneId must be UUID/GUID", + } + } + + imp.TagID = admixerExt.ZoneId + imp.BidFloor = admixerExt.CustomBidFloor + + imp.Ext = nil + + if admixerExt.CustomParams != nil { + impExt := admixerImpExt{ + CustomParams: admixerExt.CustomParams, + } + var err error + if imp.Ext, err = json.Marshal(impExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + } + + return nil +} + +func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode >= http.StatusInternalServerError { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Dsp server internal error", response.StatusCode), + }} + } + + if response.StatusCode >= http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Bad request to dsp", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + //additional no content check + if len(bidResp.SeatBid) == 0 || len(bidResp.SeatBid[0].Bid) == 0 { + return nil, nil + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), + }) + } + } + return bidResponse, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio + } + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go new file mode 100644 index 00000000000..b379e8b2910 --- /dev/null +++ b/adapters/admixer/admixer_test.go @@ -0,0 +1,10 @@ +package admixer + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "admixertest", NewAdmixerBidder("http://inv-nets.admixer.net/pbs.aspx")) +} diff --git a/adapters/admixer/admixertest/exemplary/optional-params.json b/adapters/admixer/admixertest/exemplary/optional-params.json new file mode 100644 index 00000000000..42a55ec95e8 --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/optional-params.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customFloor": 0.1, + "customParams": { + "foo": "bar" + } + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customFloor": 0.1, + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.1, + "ext": { + "customParams": { + "foo": "bar" + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.1, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-app-audio.json b/adapters/admixer/admixertest/exemplary/simple-app-audio.json new file mode 100644 index 00000000000..b8c39ead95e --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-app-audio.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "ext": { + "bidder": { + "zone": "473e443c-43d0-423d-a8d7-a302637a01d8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "tagid": "473e443c-43d0-423d-a8d7-a302637a01d8" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid" + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-app-banner.json b/adapters/admixer/admixertest/exemplary/simple-app-banner.json new file mode 100644 index 00000000000..aff4ccddd64 --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-app-banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 90, + "w": 728 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-app-native.json b/adapters/admixer/admixertest/exemplary/simple-app-native.json new file mode 100644 index 00000000000..38c005c651c --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-app-native.json @@ -0,0 +1,90 @@ + +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "zone": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "tagid": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid" + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-app-video.json b/adapters/admixer/admixertest/exemplary/simple-app-video.json new file mode 100644 index 00000000000..627023fa1e6 --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-app-video.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "zone": "ac7fa772-d7be-48cc-820b-e21728e434fe" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "tagid": "ac7fa772-d7be-48cc-820b-e21728e434fe" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "admixer", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 1024, + "h": 576 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-site-audio.json b/adapters/admixer/admixertest/exemplary/simple-site-audio.json new file mode 100644 index 00000000000..5a1d6531a85 --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-site-audio.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "ext": { + "bidder": { + "zone": "473e443c-43d0-423d-a8d7-a302637a01d8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "tagid": "473e443c-43d0-423d-a8d7-a302637a01d8" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid" + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-site-banner.json b/adapters/admixer/admixertest/exemplary/simple-site-banner.json new file mode 100644 index 00000000000..bd50aba8d1a --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-site-banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 90, + "w": 728 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-site-native.json b/adapters/admixer/admixertest/exemplary/simple-site-native.json new file mode 100644 index 00000000000..246d02025b1 --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-site-native.json @@ -0,0 +1,90 @@ + +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "zone": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "tagid": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "admixer", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid" + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/exemplary/simple-site-video.json b/adapters/admixer/admixertest/exemplary/simple-site-video.json new file mode 100644 index 00000000000..42d771ce86b --- /dev/null +++ b/adapters/admixer/admixertest/exemplary/simple-site-video.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "zone": "ac7fa772-d7be-48cc-820b-e21728e434fe" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "tagid": "ac7fa772-d7be-48cc-820b-e21728e434fe" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "admixer", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 1024, + "h": 576 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/admixer/admixertest/params/race/audio.json b/adapters/admixer/admixertest/params/race/audio.json new file mode 100644 index 00000000000..f9aa771e4b1 --- /dev/null +++ b/adapters/admixer/admixertest/params/race/audio.json @@ -0,0 +1,5 @@ +{ + "zone": "473e443c-43d0-423d-a8d7-a302637a01d8", + "customFloor": 0.1, + "customParams": {"foo": "bar"} +} \ No newline at end of file diff --git a/adapters/admixer/admixertest/params/race/banner.json b/adapters/admixer/admixertest/params/race/banner.json new file mode 100644 index 00000000000..03510f7e1ca --- /dev/null +++ b/adapters/admixer/admixertest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customFloor": 0.1, + "customParams": {"foo": "bar"} +} diff --git a/adapters/admixer/admixertest/params/race/native.json b/adapters/admixer/admixertest/params/race/native.json new file mode 100644 index 00000000000..65712a30228 --- /dev/null +++ b/adapters/admixer/admixertest/params/race/native.json @@ -0,0 +1,5 @@ +{ + "zone": "b1fbebfc-7155-4922-bb86-615e7f3d6eef", + "customFloor": 0.1, + "customParams": {"foo": "bar"} +} \ No newline at end of file diff --git a/adapters/admixer/admixertest/params/race/video.json b/adapters/admixer/admixertest/params/race/video.json new file mode 100644 index 00000000000..5e9bc6e59fd --- /dev/null +++ b/adapters/admixer/admixertest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "zone": "ac7fa772-d7be-48cc-820b-e21728e434fe", + "customFloor": 0.1, + "customParams": {"foo": "bar"} +} diff --git a/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json b/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json new file mode 100644 index 00000000000..5256c14050b --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request to dsp", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json b/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json new file mode 100644 index 00000000000..1c06eadce44 --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Dsp server internal error", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/admixertest/supplemental/ext-unmarshall-error.json b/adapters/admixer/admixertest/supplemental/ext-unmarshall-error.json new file mode 100644 index 00000000000..e14a26356f8 --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/ext-unmarshall-error.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "format": [ + { + "w": 728, + "h": 90 + } + ], + "ext": { + "bidder": { + "zone": [ + "ec943cb9-61ec-460f-a925-6489c3fcc4e3" + ] + } + } + } + ], + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Wrong Admixer bidder ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json b/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json new file mode 100644 index 00000000000..972f2f5dd01 --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://inv-nets.admixer.net/pbs.aspx", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" + } + ] + } + }, + "mockResponse": { + "status": 301, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 301", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/admixertest/supplemental/wrong-zone-id-error.json b/adapters/admixer/admixertest/supplemental/wrong-zone-id-error.json new file mode 100644 index 00000000000..2f2dcec1a50 --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/wrong-zone-id-error.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "format": [{ + "w": 728, + "h": 90 + }], + "ext": { + "bidder": { + "zone": "12345" + } + } + } + ], + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "ZoneId must be UUID/GUID", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/admixertest/supplemental/zero-bid-request-error.json b/adapters/admixer/admixertest/supplemental/zero-bid-request-error.json new file mode 100644 index 00000000000..a43d9b5fa65 --- /dev/null +++ b/adapters/admixer/admixertest/supplemental/zero-bid-request-error.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + ], + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impressions in request", + "comparison": "literal" + } + ] +} diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go new file mode 100644 index 00000000000..71cccb6a3da --- /dev/null +++ b/adapters/admixer/params_test.go @@ -0,0 +1,57 @@ +package admixer + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +// This file actually intends to test static/bidder-params/admixer.json +// +// These also validate the format of the external API: request.imp[i].ext.admixer + +// TestValidParams makes sure that the admixer schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdmixer, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected admixer params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the admixer schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdmixer, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21"}`, + `{"zone": "9ff668a2-4122-462e-aaf8-36ea3a54ba21"}`, + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": 0.1}`, + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": {"foo": "bar"}}`, + `{"zone": "9ff668a2-4122-462e-aaf8-36ea3a54ba21", "customFloor": 0.1, "customParams": {"foo": ["bar", "baz"]}}`, +} + +var invalidParams = []string{ + `{"zone": "123"}`, + `{"zone": ""}`, + `{"zone": "ZFF668A2-4122-462E-AAF8-36EA3A54BA21"}`, + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA211"}`, + `{"zone": "123", "customFloor": "0.1"}`, + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": -0.1}`, + `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": "foo: bar"}`, +} diff --git a/adapters/admixer/usersync.go b/adapters/admixer/usersync.go new file mode 100644 index 00000000000..0a7f50ab79a --- /dev/null +++ b/adapters/admixer/usersync.go @@ -0,0 +1,11 @@ +package admixer + +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" + "text/template" +) + +func NewAdmixerSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("admixer", 511, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/admixer/usersync_test.go b/adapters/admixer/usersync_test.go new file mode 100644 index 00000000000..a5715c64a46 --- /dev/null +++ b/adapters/admixer/usersync_test.go @@ -0,0 +1,34 @@ +package admixer + +import ( + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" + "testing" + "text/template" +) + +func TestAdmixerSyncer(t *testing.T) { + syncURL := "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl=localhost%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdmixerSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Value: "C", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://inv-nets.admixer.net/adxcm.aspx?gdpr=A&gdpr_consent=B&us_privacy=C&redir=1&rurl=localhost%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 511, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 52686422039..f582201d517 100644 --- a/config/config.go +++ b/config/config.go @@ -491,6 +491,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") @@ -673,6 +674,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}") + v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index d169c1204bf..4c6da00f337 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -12,6 +12,7 @@ import ( "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" + "github.com/prebid/prebid-server/adapters/admixer" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adtelligent" @@ -72,6 +73,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdform: adform.NewAdformBidder(client, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), openrtb_ext.BidderAdkernel: adkernel.NewAdkernelAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernel))].Endpoint), openrtb_ext.BidderAdkernelAdn: adkernelAdn.NewAdkernelAdnAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernelAdn))].Endpoint), + openrtb_ext.BidderAdmixer: admixer.NewAdmixerBidder(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdmixer))].Endpoint), openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), openrtb_ext.BidderAdpone: adpone.NewAdponeBidder(cfg.Adapters[string(openrtb_ext.BidderAdpone)].Endpoint), openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), diff --git a/go.mod b/go.mod index 5c837c2ee7b..af4bf5570a5 100644 --- a/go.mod +++ b/go.mod @@ -7,17 +7,23 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect + github.com/aerospike/aerospike-client-go v2.7.2+incompatible github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible + github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 + github.com/didip/tollbooth v4.0.2+incompatible github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd + github.com/go-redis/redis v6.15.7+incompatible + github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5 github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/snappy v0.0.1 github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb v1.6.1 // indirect github.com/julienschmidt/httprouter v1.1.0 @@ -31,8 +37,10 @@ require ( github.com/mxmCherry/openrtb v11.0.0+incompatible github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/prebid/go-gdpr v0.6.0 + github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect @@ -40,14 +48,15 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.5.0 github.com/sergi/go-diff v1.0.0 // indirect + github.com/sirupsen/logrus v1.4.2 github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.1.1 // indirect github.com/spf13/cast v1.2.0 // indirect github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect github.com/spf13/pflag v1.0.2 // indirect github.com/spf13/viper v1.1.0 - github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 + github.com/valyala/fasthttp v1.9.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -55,8 +64,10 @@ require ( github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/net v0.0.0-20180906233101-161cd47e91fd + github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect golang.org/x/text v0.3.0 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 57fcbb76428..06f07b1ece0 100644 --- a/go.sum +++ b/go.sum @@ -6,35 +6,57 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/aerospike/aerospike-client-go v2.7.2+incompatible h1:bWbRf8trg1FhKF7u43KLGNfOH60RlvIgQjpaS107DZ8= +github.com/aerospike/aerospike-client-go v2.7.2+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coocood/freecache v1.0.1 h1:oFyo4msX2c0QIKU+kuMJUwsKamJ+AKc2JJrKcMszJ5M= github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= +github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5 h1:ZZVxQRCm4ewuoqqLBJ7LHpsk4OGx2PkyCsRKLq4oHgE= +github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -45,6 +67,17 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -66,18 +99,20 @@ github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prebid/go-gdpr v0.6.0 h1:/GKrygGkUbsgd96HIkjAu7/6CHtRedvcojRtfAd4Igc= github.com/prebid/go-gdpr v0.6.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= +github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= +github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= @@ -88,6 +123,8 @@ github.com/rs/cors v1.5.0 h1:dgSHE6+ia18arGOTIYQKKGWLvEbGvmbNE6NfxhoNHUY= github.com/rs/cors v1.5.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= @@ -103,8 +140,14 @@ github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw= +github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 h1:Va10CytCCYRm4xBTses5ZDeDjeIQjhaiC9nRCe/yflI= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303/go.mod h1:Xdcad1nGVhQfhoV0go+/4WaI/RZkWlvfjkVCdpMTxPY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -119,21 +162,34 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 6e70ef4b6fa..3ae443410b9 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -25,6 +25,7 @@ const ( BidderAdkernel BidderName = "adkernel" BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdpone BidderName = "adpone" + BidderAdmixer BidderName = "admixer" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderApplogy BidderName = "applogy" @@ -80,6 +81,7 @@ var BidderMap = map[string]BidderName{ "adform": BidderAdform, "adkernel": BidderAdkernel, "adkernelAdn": BidderAdkernelAdn, + "admixer": BidderAdmixer, "adpone": BidderAdpone, "adtelligent": BidderAdtelligent, "advangelists": BidderAdvangelists, diff --git a/openrtb_ext/imp_admixer.go b/openrtb_ext/imp_admixer.go new file mode 100644 index 00000000000..ce122ae0029 --- /dev/null +++ b/openrtb_ext/imp_admixer.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpAdmixer struct { + ZoneId string `json:"zone"` + CustomBidFloor float64 `json:"customFloor"` + CustomParams map[string]interface{} `json:"customParams"` +} diff --git a/static/bidder-info/admixer.yaml b/static/bidder-info/admixer.yaml new file mode 100644 index 00000000000..64ad2024058 --- /dev/null +++ b/static/bidder-info/admixer.yaml @@ -0,0 +1,15 @@ +maintainer: + email: "prebid@admixer.net" +capabilities: + app: + mediaTypes: + - banner + - video + - native + - audio + site: + mediaTypes: + - banner + - video + - native + - audio \ No newline at end of file diff --git a/static/bidder-params/admixer.json b/static/bidder-params/admixer.json new file mode 100644 index 00000000000..886e33ff2bb --- /dev/null +++ b/static/bidder-params/admixer.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Admixer Adapter Params", + "description": "A schema which validates params accepted by the Admixer adapter", + + "type": "object", + "properties": { + "zone": { + "type": "string", + "description": "Zone ID.", + "pattern": "^([a-fA-F\\d\\-]{36})$" + }, + "customFloor": { + "type": "number", + "description": "The minimum CPM price in USD.", + "minimum": 0 + }, + "customParams": { + "type": "object", + "description": "User-defined targeting key-value pairs." + } + }, + + "required": ["zone"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 5447cd28800..7f65c7f476f 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -4,13 +4,13 @@ import ( "strings" "text/template" - "github.com/prebid/prebid-server/adapters/adpone" - "github.com/golang/glog" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" + "github.com/prebid/prebid-server/adapters/admixer" + "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/appnexus" @@ -68,6 +68,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdmixer, admixer.NewAdmixerSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdpone, adpone.NewadponeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 87a9caebf96..2a8d1fd1b0b 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -18,6 +18,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdform): syncConfig, string(openrtb_ext.BidderAdkernel): syncConfig, string(openrtb_ext.BidderAdkernelAdn): syncConfig, + string(openrtb_ext.BidderAdmixer): syncConfig, string(openrtb_ext.BidderAdpone): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, From 2e806517d34fa7bb9a8dfcd819915cf55e96b8a6 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Tue, 3 Mar 2020 06:59:15 -0800 Subject: [PATCH 020/603] Adding copying of gdpr consent string to openrtb bid request (#1189) * Adding copying of gdpr consent string to openrtb bid request * Updated video request to use OpenRTB Video and User objects * Fixing unit test failure message * Updates from code review comments * Updating unit test initialization * Updated mimes array construction --- endpoints/openrtb2/video_auction.go | 50 +++++++------- endpoints/openrtb2/video_auction_test.go | 66 +++++++++++++++--- openrtb_ext/bid_request_video.go | 87 +----------------------- 3 files changed, 81 insertions(+), 122 deletions(-) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 7c9651af747..feb8de193e7 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -328,12 +328,12 @@ func max(a, b int) int { return b } -func createImpressionTemplate(imp openrtb.Imp, video openrtb_ext.SimplifiedVideo) openrtb.Imp { +func createImpressionTemplate(imp openrtb.Imp, video *openrtb.Video) openrtb.Imp { imp.Video = &openrtb.Video{} imp.Video.W = video.W imp.Video.H = video.H imp.Video.Protocols = video.Protocols - imp.Video.MIMEs = video.Mimes + imp.Video.MIMEs = video.MIMEs return imp } @@ -471,14 +471,7 @@ func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb.Bi bidRequest.Device = &videoRequest.Device } - if &videoRequest.User != nil { - bidRequest.User = &openrtb.User{ - BuyerUID: videoRequest.User.Buyeruids["appnexus"], //TODO: map to string merging - Yob: videoRequest.User.Yob, - Gender: videoRequest.User.Gender, - Keywords: videoRequest.User.Keywords, - } - } + bidRequest.User = videoRequest.User if len(videoRequest.BCat) != 0 { bidRequest.BCat = videoRequest.BCat @@ -660,27 +653,30 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo) } } - if len(req.Video.Mimes) == 0 { - err := errors.New("request missing required field: Video.Mimes") - errL = append(errL, err) - } else { - mimes := make([]string, 0, 0) - for _, mime := range req.Video.Mimes { - if mime != "" { - mimes = append(mimes, mime) + if req.Video != nil { + if len(req.Video.MIMEs) == 0 { + err := errors.New("request missing required field: Video.Mimes") + errL = append(errL, err) + } else { + mimes := make([]string, 0, len(req.Video.MIMEs)) + for _, mime := range req.Video.MIMEs { + if mime != "" { + mimes = append(mimes, mime) + } } + if len(mimes) == 0 { + err := errors.New("request missing required field: Video.Mimes, mime types contains empty strings only") + errL = append(errL, err) + } + req.Video.MIMEs = mimes } - if len(mimes) == 0 { - err := errors.New("request missing required field: Video.Mimes, mime types contains empty strings only") + + if len(req.Video.Protocols) == 0 { + err := errors.New("request missing required field: Video.Protocols") errL = append(errL, err) } - if len(mimes) > 0 { - req.Video.Mimes = mimes - } - } - - if len(req.Video.Protocols) == 0 { - err := errors.New("request missing required field: Video.Protocols") + } else { + err := errors.New("request missing required field: Video") errL = append(errL, err) } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index cd87041055a..dfe2a6a50b8 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -233,8 +233,8 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: openrtb_ext.SimplifiedVideo{ - Mimes: mimes, + Video: &openrtb.Video{ + MIMEs: mimes, Protocols: videoProtocols, }, } @@ -271,8 +271,8 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 0, }, - Video: openrtb_ext.SimplifiedVideo{ - Mimes: mimes, + Video: &openrtb.Video{ + MIMEs: mimes, Protocols: videoProtocols, }, } @@ -345,8 +345,8 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: openrtb_ext.SimplifiedVideo{ - Mimes: mimes, + Video: &openrtb.Video{ + MIMEs: mimes, Protocols: videoProtocols, }, } @@ -418,8 +418,8 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: openrtb_ext.SimplifiedVideo{ - Mimes: mimes, + Video: &openrtb.Video{ + MIMEs: mimes, Protocols: videoProtocols, }, } @@ -473,8 +473,8 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: openrtb_ext.SimplifiedVideo{ - Mimes: mimes, + Video: &openrtb.Video{ + MIMEs: mimes, Protocols: videoProtocols, }, } @@ -484,6 +484,43 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { assert.Len(t, podErrors, 0, "Pod errors should be empty") } +func TestVideoEndpointValidationsMissingVideo(t *testing.T) { + ex := &mockExchangeVideo{} + deps := mockDeps(t, ex) + deps.cfg.VideoStoredRequestRequired = true + + req := openrtb_ext.BidRequestVideo{ + StoredRequestId: "123", + PodConfig: openrtb_ext.PodConfig{ + DurationRangeSec: []int{15, 30}, + RequireExactDuration: true, + Pods: []openrtb_ext.Pod{ + { + PodId: 1, + AdPodDurationSec: 30, + ConfigId: "qwerty", + }, + { + PodId: 2, + AdPodDurationSec: 30, + ConfigId: "qwerty", + }, + }, + }, + App: &openrtb.App{ + Bundle: "pbs.com", + }, + IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ + PrimaryAdserver: 1, + }, + } + + errors, podErrors := deps.validateVideoRequest(&req) + assert.Len(t, podErrors, 0, "Pod errors should be empty") + assert.Len(t, errors, 1, "Errors array should contain 1 error message") + assert.Equal(t, "request missing required field: Video", errors[0].Error(), "Errors array should contain message regarding missing Video field") +} + func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { openRtbBidResp := openrtb.BidResponse{} podErrors := make([]PodError, 0) @@ -633,6 +670,13 @@ func TestMergeOpenRTBToVideoRequest(t *testing.T) { Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"1NYY","existing":"any","consent":"anyConsent"}`), } + videoReq.User = &openrtb.User{ + BuyerUID: "test UID", + Yob: 1980, + Keywords: "test keywords", + Ext: json.RawMessage(`{"consent":"test string"}`), + } + mergeData(videoReq, bidReq) assert.Equal(t, videoReq.BCat, bidReq.BCat, "BCat is incorrect") @@ -647,6 +691,8 @@ func TestMergeOpenRTBToVideoRequest(t *testing.T) { assert.Equal(t, videoReq.Site.Page, bidReq.Site.Page, "Device.Site.Page is incorrect") assert.Equal(t, videoReq.Regs, bidReq.Regs, "Regs is incorrect") + + assert.Equal(t, videoReq.User, bidReq.User, "User is incorrect") } func TestHandleError(t *testing.T) { diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index f7ddf203294..18865108433 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -43,7 +43,7 @@ type BidRequestVideo struct { // object; optional // Description: // Container object for the user of of the actual device - User SimplifiedUser `json:"user,omitempty"` + User *openrtb.User `json:"user,omitempty"` // Attribute: // device @@ -67,7 +67,7 @@ type BidRequestVideo struct { // object; required // Description: // Player container object - Video SimplifiedVideo `json:"video,omitempty"` + Video *openrtb.Video `json:"video,omitempty"` // Attribute: // content @@ -225,86 +225,3 @@ type Cacheconfig struct { // Time to Live for a cache entry specified in seconds Ttl int `json:"ttl"` } - -type Gdpr struct { - // Attribute: - // consentrequired - // Type: - // boolean; optional - // Indicates whether GDPR is in effect - ConsentRequired bool `json:"consentrequired"` - - // Attribute: - // consentstring - // Type: - // string; optional - // Contains the data structure developed by the GDPR - ConsentString string `json:"consentstring"` -} - -type SimplifiedUser struct { - // Attribute: - // buyeruids - // Type: - // map; optional - // ID of the stored config that corresponds to a single pod request - Buyeruids map[string]string `json:"buyeruids"` - - // Attribute: - // gdpr - // Type: - // object; optional - // Container object for GDPR - Gdpr Gdpr `json:"gdpr"` - - // Attribute: - // yob - // Type: - // int; optional - // Year of birth as a 4-digit integer - Yob int64 `json:"yob"` - - // Attribute: - // gender - // Type: - // string; optional - // Gender, where “M” = male, “F” = female, “O” = known to be other - Gender string `json:"gender"` - - // Attribute: - // keywords - // Type: - // string; optional - // Comma separated list of keywords, interests, or intent. - Keywords string `json:"keywords"` -} - -type SimplifiedVideo struct { - // Attribute: - // w - // Type: - // uint64; optional - // Width of video - W uint64 `json:"w"` - - // Attribute: - // h - // Type: - // uint64; optional - // Height of video - H uint64 `json:"h"` - - // Attribute: - // mimes - // Type: - // array of strings; optional - // Video mime types - Mimes []string `json:"mimes"` - - // Attribute: - // protocols - // Type: - // array of objects; optional - // protocols - Protocols []openrtb.Protocol `json:"protocols"` -} From c6919ee17741b2ed5f3ffbb02d6d24ecefc629be Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Wed, 4 Mar 2020 07:28:54 -0800 Subject: [PATCH 021/603] fix conversant sync pixel (#1208) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index f582201d517..7c9ffa71672 100644 --- a/config/config.go +++ b/config/config.go @@ -497,7 +497,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match/bounce/current?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26networkId%3D72582%26version%3D1%26uid%3D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26networkId%3D72582%26version%3D1%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") From f03dfa56d7689502e462ad306eb273075237fae7 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 4 Mar 2020 10:25:38 -0800 Subject: [PATCH 022/603] openx adapter: forward bid response currency in openx adapter if set (#1211) it was always set to the default USD before --- adapters/openx/openx.go | 5 +++++ adapters/openx/openx_test.go | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index dd176813820..63e8e697869 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -169,6 +169,11 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + // overrride default currency + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } + for _, sb := range bidResp.SeatBid { for i := range sb.Bid { bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index f7765d846ad..f79eb062531 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -1,11 +1,48 @@ package openx import ( + "encoding/json" "testing" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "openxtest", NewOpenxBidder("http://rtb.openx.net/prebid")) } + +func TestResponseWithCurrencies(t *testing.T) { + assertCurrencyInBidResponse(t, "USD", nil) + + currency := "USD" + assertCurrencyInBidResponse(t, "USD", ¤cy) + + currency = "EUR" + assertCurrencyInBidResponse(t, "EUR", ¤cy) +} + +func assertCurrencyInBidResponse(t *testing.T, expectedCurrency string, currency *string) { + + bidder := NewOpenxBidder("http://rtb.openx.net/prebid") + prebidRequest := &openrtb.BidRequest{ + Imp: []openrtb.Imp{}, + } + mockedBidResponse := &openrtb.BidResponse{} + if currency != nil { + mockedBidResponse.Cur = *currency + } + body, _ := json.Marshal(mockedBidResponse) + responseData := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + bidResponse, errs := bidder.MakeBids(prebidRequest, nil, responseData) + + if errs != nil { + t.Fatalf("Failed to make bids %v", errs) + } + assert.Equal(t, expectedCurrency, bidResponse.Currency) +} From 8686f03a46ad51ccc4a96f9756628abdc8e60176 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 5 Mar 2020 10:41:38 -0500 Subject: [PATCH 023/603] add ucfunnel adapter (#1192) --- adapters/ucfunnel/params_test.go | 47 +++++ adapters/ucfunnel/ucfunnel.go | 150 ++++++++++++++++ adapters/ucfunnel/ucfunnel_test.go | 163 ++++++++++++++++++ .../ucfunneltest/exemplary/ucfunnel.json | 103 +++++++++++ .../ucfunneltest/params/race/banner.json | 5 + .../ucfunneltest/params/race/video.json | 5 + adapters/ucfunnel/usersync.go | 12 ++ adapters/ucfunnel/usersync_test.go | 30 ++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_ucfunnel.go | 7 + static/bidder-info/ucfunnel.yaml | 11 ++ static/bidder-params/ucfunnel.json | 17 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 16 files changed, 559 insertions(+) create mode 100644 adapters/ucfunnel/params_test.go create mode 100644 adapters/ucfunnel/ucfunnel.go create mode 100644 adapters/ucfunnel/ucfunnel_test.go create mode 100644 adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json create mode 100644 adapters/ucfunnel/ucfunneltest/params/race/banner.json create mode 100644 adapters/ucfunnel/ucfunneltest/params/race/video.json create mode 100644 adapters/ucfunnel/usersync.go create mode 100644 adapters/ucfunnel/usersync_test.go create mode 100644 openrtb_ext/imp_ucfunnel.go create mode 100644 static/bidder-info/ucfunnel.yaml create mode 100644 static/bidder-params/ucfunnel.json diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go new file mode 100644 index 00000000000..4faec8739da --- /dev/null +++ b/adapters/ucfunnel/params_test.go @@ -0,0 +1,47 @@ +package ucfunnel + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +// This file actually intends to test static/bidder-params/ucfunnel.json +// +// These also validate the format of the external API: request.imp[i].ext.ucfunnel + +// TestValidParams makes sure that the ucfunnel schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderUcfunnel, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected ucfunnel params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the ucfunnel schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderUcfunnel, json.RawMessage(invalidParam)); err != nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"adunitid": "ad-83444226E44368D1E32E49EEBE6D29","partnerid": "par-2EDDB423AA24474188B843EE4842932"}`, +} + +var invalidParams = []string{ + `{"adunitid": "","partnerid": ""}`, +} diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go new file mode 100644 index 00000000000..2c64e81205b --- /dev/null +++ b/adapters/ucfunnel/ucfunnel.go @@ -0,0 +1,150 @@ +package ucfunnel + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "net/url" +) + +type UcfunnelAdapter struct { + URI string +} + +func NewUcfunnelBidder(endpoint string) *UcfunnelAdapter { + return &UcfunnelAdapter{ + URI: endpoint} +} + +func (a *UcfunnelAdapter) Name() string { + return "ucfunnel" +} + +func (a *UcfunnelAdapter) SkipNoCookies() bool { + return false +} + +func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var errs []error + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + var bidReq openrtb.BidRequest + if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType := getBidType(bidReq, sb.Bid[i].ImpID) + if (bidType == openrtb_ext.BidTypeBanner) || (bidType == openrtb_ext.BidTypeVideo) { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +func (a *UcfunnelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + + // If all the requests were malformed, don't bother making a server call with no impressions. + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("No impression in the bid request\n"), + }} + } + + partnerId, partnerErr := getPartnerId(request) + if partnerErr != nil { + return nil, partnerErr + } + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json") + + uri := a.URI + "/" + url.PathEscape(partnerId) + "/request" + return []*adapters.RequestData{{ + Method: "POST", + Uri: uri, + Body: reqJSON, + Headers: headers, + }}, errs +} + +func getPartnerId(request *openrtb.BidRequest) (string, []error) { + var ext ExtBidderUcfunnel + var errs = []error{} + err := json.Unmarshal(request.Imp[0].Ext, &ext) + if err != nil { + errs = append(errs, err) + return "", errs + } + errs = checkBidderParameter(ext) + if errs != nil { + return "", errs + } + return ext.Bidder.PartnerId, nil +} + +func checkBidderParameter(ext ExtBidderUcfunnel) []error { + var errs = []error{} + if len(ext.Bidder.PartnerId) == 0 || len(ext.Bidder.AdUnitId) == 0 { + errs = append(errs, fmt.Errorf("No PartnerId or AdUnitId in the bid request\n")) + return errs + } + return nil +} + +func getBidType(bidReq openrtb.BidRequest, impid string) openrtb_ext.BidType { + for i := range bidReq.Imp { + if bidReq.Imp[i].ID == impid { + if bidReq.Imp[i].Banner != nil { + return openrtb_ext.BidTypeBanner + } else if bidReq.Imp[i].Video != nil { + return openrtb_ext.BidTypeVideo + } else if bidReq.Imp[i].Audio != nil { + return openrtb_ext.BidTypeAudio + } else if bidReq.Imp[i].Native != nil { + return openrtb_ext.BidTypeNative + } + } + } + return openrtb_ext.BidTypeNative +} + +type ExtBidderUcfunnel struct { + Bidder openrtb_ext.ExtImpUcfunnel `json:"bidder"` +} diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go new file mode 100644 index 00000000000..60d796fdf99 --- /dev/null +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -0,0 +1,163 @@ +package ucfunnel + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestUcfunnelAdapterNames(t *testing.T) { + adapter := NewUcfunnelBidder("http://localhost/bid") + adapterstest.VerifyStringValue(adapter.Name(), "ucfunnel", t) +} + +func TestSkipNoCookies(t *testing.T) { + adapter := NewUcfunnelBidder("http://localhost/bid") + status := adapter.SkipNoCookies() + if status != false { + t.Errorf("actual = %t expected != %t", status, false) + } +} + +func TestMakeRequests(t *testing.T) { + + imp := openrtb.Imp{ + ID: "1234", + Banner: &openrtb.Banner{}, + } + imp2 := openrtb.Imp{ + ID: "1235", + Video: &openrtb.Video{}, + } + + imp3 := openrtb.Imp{ + ID: "1236", + Audio: &openrtb.Audio{}, + } + + imp4 := openrtb.Imp{ + ID: "1237", + Native: &openrtb.Native{}, + } + imp5 := openrtb.Imp{ + ID: "1237", + Native: &openrtb.Native{}, + } + + internalRequest01 := openrtb.BidRequest{Imp: []openrtb.Imp{}} + internalRequest02 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} + + internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[2].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[3].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[4].Ext = []byte(`{"bidder": {"adunitid": "aa","partnerid": ""}}`) + + adapter := NewUcfunnelBidder("http://localhost/bid") + + var testCases = []struct { + in []openrtb.BidRequest + out1 [](int) + out2 [](bool) + }{ + { + in: []openrtb.BidRequest{internalRequest01, internalRequest02, internalRequest03}, + out1: [](int){1, 1, 0}, + out2: [](bool){false, false, true}, + }, + } + + for idx := range testCases { + for i := range testCases[idx].in { + RequestData, err := adapter.MakeRequests(&testCases[idx].in[i], nil) + if ((RequestData == nil) == testCases[idx].out2[i]) && (len(err) == testCases[idx].out1[i]) { + t.Errorf("actual = %v expected = %v", len(err), testCases[idx].out1[i]) + } + } + } +} + +func TestMakeBids(t *testing.T) { + imp := openrtb.Imp{ + ID: "1234", + Banner: &openrtb.Banner{}, + } + imp2 := openrtb.Imp{ + ID: "1235", + Video: &openrtb.Video{}, + } + + imp3 := openrtb.Imp{ + ID: "1236", + Audio: &openrtb.Audio{}, + } + + imp4 := openrtb.Imp{ + ID: "1237", + Native: &openrtb.Native{}, + } + imp5 := openrtb.Imp{ + ID: "1237", + Native: &openrtb.Native{}, + } + + internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest04 := openrtb.BidRequest{Imp: []openrtb.Imp{imp}} + + internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[2].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[3].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) + internalRequest03.Imp[4].Ext = []byte(`{"bidder": {"adunitid": "aa","partnerid": ""}}`) + internalRequest04.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "0"}}`) + + mockResponse200 := adapters.ResponseData{StatusCode: 200, Body: json.RawMessage(`{"seatbid": [{"bid": [{"impid": "1234"}]},{"bid": [{"impid": "1235"}]},{"bid": [{"impid": "1236"}]},{"bid": [{"impid": "1237"}]}]}`)} + mockResponse203 := adapters.ResponseData{StatusCode: 203, Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} + mockResponse204 := adapters.ResponseData{StatusCode: 204, Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} + mockResponse400 := adapters.ResponseData{StatusCode: 400, Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} + mockResponseError := adapters.ResponseData{StatusCode: 200, Body: json.RawMessage(`{"seatbid":[{"bid":[{"im236"}],{"bid":[{"impid":"1237}]}`)} + + RequestData01 := adapters.RequestData{Method: "POST", Body: []byte(`{"imp":[{"id":"1234","banner":{}},{"id":"1235","video":{}},{"id":"1236","audio":{}},{"id":"1237","native":{}}]}`)} + RequestData02 := adapters.RequestData{Method: "POST", Body: []byte(`{"imp":[{"id":"1234","banne"1235","video":{}},{"id":"1236","audio":{}},{"id":"1237","native":{}}]}`)} + + adapter := NewUcfunnelBidder("http://localhost/bid") + + var testCases = []struct { + in1 []openrtb.BidRequest + in2 []adapters.RequestData + in3 []adapters.ResponseData + out1 [](bool) + out2 [](bool) + }{ + { + in1: []openrtb.BidRequest{internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest04}, + in2: []adapters.RequestData{RequestData01, RequestData01, RequestData01, RequestData01, RequestData01, RequestData02}, + in3: []adapters.ResponseData{mockResponse200, mockResponse203, mockResponse204, mockResponse400, mockResponseError, mockResponse200}, + out1: [](bool){true, false, false, false, false, false}, + out2: [](bool){false, true, false, true, true, true}, + }, + } + + for idx := range testCases { + for i := range testCases[idx].in1 { + BidderResponse, err := adapter.MakeBids(&testCases[idx].in1[i], &testCases[idx].in2[i], &testCases[idx].in3[i]) + + if (BidderResponse == nil) == testCases[idx].out1[i] { + fmt.Println(i) + fmt.Println("BidderResponse") + t.Errorf("actual = %t expected == %v", (BidderResponse == nil), testCases[idx].out1[i]) + } + + if (err == nil) == testCases[idx].out2[i] { + fmt.Println(i) + fmt.Println("error") + t.Errorf("actual = %t expected == %v", err, testCases[idx].out2[i]) + } + } + } +} diff --git a/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json b/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json new file mode 100644 index 00000000000..2a7e4b2b861 --- /dev/null +++ b/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json @@ -0,0 +1,103 @@ +{ + "imp": [ + { + "id": "1", + "banner": { + "w": 970, + "h": 250, + "mimes": [ + "image/gif", + "image/jpeg", + "image/png", + "text/html", + "text/javascript", + "application/javascript" + ], + "pos": 1, + "battr": [ + 6, + 7 + ], + "topframe": 1 + }, + "instl": 0, + "displaymanager": "aralego.com", + "displaymanagerver": "v1.0.0", + "secure": 0, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "exp": 3600, + "ext":{ + "ucfunnel":{ + "adunitid":"ad-BE7E9EB323E9996218A733887B6E924" + } + } + } + ], + "id": "c901f218-4ca6-480b-97dc-c6fd50e24544", + "at": 2, + "tmax": 300, + "bcat": [ + "IAB11-1" + ], + "badv": [ + "abc.com", + "cbn.com", + "xyz.com" + ], + "regs": { + "coppa": 0, + "ext": { + "gdpr": 0 + }, + "us_privacy": "1--" + }, + "site": { + "id": "5b88fd05beffd764bb0f7a3a", + "name": "test", + "page": "http://127.0.0.1:8000/tmp66.html", + "domain": "127.0.0.1", + "cat": [ + "IAB1" + ], + "publisher": { + "id": "par-7E6D2DB9A8922AB07B44A444D2BA67" + } + }, + "device": { + "w": 1440, + "h": 900, + "dnt": 1, + "ip": "127.0.0.1", + "js": 1, + "os": "MacOS", + "osv": "10.15.1", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36", + "language": "zh-TW", + "devicetype": 2, + "geo": { + "country": "unknown", + "type": 2 + } + }, + "user": { + "id": "e7bf9b85-9554-441c-964e-c8112d35d17b" + }, + "source": { + "fd": 1, + "ext": { + "schain": { + "complete": 1, + "ver": "1.0", + "nodes": [ + { + "asi": "aralego.com", + "sid": "par-7E6D2DB9A8922AB07B44A444D2BA67", + "rid": "c8f800c2-d285-4cb9-8fc9-f95df52f6e0c", + "hp": 1 + } + ] + } + } + } +} \ No newline at end of file diff --git a/adapters/ucfunnel/ucfunneltest/params/race/banner.json b/adapters/ucfunnel/ucfunneltest/params/race/banner.json new file mode 100644 index 00000000000..2c8c2e1e198 --- /dev/null +++ b/adapters/ucfunnel/ucfunneltest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "adunitid": "ad-83444226E44368D1E32E49EEBE6D29", + "partnerid": "par-2EDDB423AA24474188B843EE4842932" +} + \ No newline at end of file diff --git a/adapters/ucfunnel/ucfunneltest/params/race/video.json b/adapters/ucfunnel/ucfunneltest/params/race/video.json new file mode 100644 index 00000000000..0a562b34aa1 --- /dev/null +++ b/adapters/ucfunnel/ucfunneltest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "adunitid": "ad-E2B22B678D6A664E092824848D26BB2", + "partnerid": "par-2EDDB423AA24474188B843EE4842932" +} + \ No newline at end of file diff --git a/adapters/ucfunnel/usersync.go b/adapters/ucfunnel/usersync.go new file mode 100644 index 00000000000..92eba0d73e0 --- /dev/null +++ b/adapters/ucfunnel/usersync.go @@ -0,0 +1,12 @@ +package ucfunnel + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewUcfunnelSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("ucfunnel", 607, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/ucfunnel/usersync_test.go b/adapters/ucfunnel/usersync_test.go new file mode 100644 index 00000000000..45320b8cac1 --- /dev/null +++ b/adapters/ucfunnel/usersync_test.go @@ -0,0 +1,30 @@ +package ucfunnel + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestUcfunnelSyncer(t *testing.T) { + syncURL := "//sync.aralego.com/idsync?gdpr={{.GDPR}}&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewUcfunnelSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//sync.aralego.com/idsync?gdpr=0&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 607, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 7c9ffa71672..b9501cf6355 100644 --- a/config/config.go +++ b/config/config.go @@ -529,6 +529,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderTappx doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. @@ -719,6 +720,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20") + v.SetDefault("adapters.ucfunnel.endpoint", "http://apac-hk-adx.aralego.com/prebid") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.verizonmedia.disabled", true) v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 4c6da00f337..f0ff9c5b1a7 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -55,6 +55,7 @@ import ( "github.com/prebid/prebid-server/adapters/tappx" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" + "github.com/prebid/prebid-server/adapters/ucfunnel" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" @@ -123,6 +124,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderTappx: tappx.NewTappxBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTappx))].Endpoint), openrtb_ext.BidderTriplelift: triplelift.NewTripleliftBidder(client, cfg.Adapters[string(openrtb_ext.BidderTriplelift)].Endpoint), openrtb_ext.BidderTripleliftNative: triplelift_native.NewTripleliftNativeBidder(client, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].ExtraAdapterInfo), + openrtb_ext.BidderUcfunnel: ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint), openrtb_ext.BidderUnruly: unruly.NewUnrulyBidder(client, cfg.Adapters[string(openrtb_ext.BidderUnruly)].Endpoint), openrtb_ext.BidderVerizonMedia: verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint), openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 3ae443410b9..d1490603b50 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -68,6 +68,7 @@ const ( BidderTappx BidderName = "tappx" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" + BidderUcfunnel BidderName = "ucfunnel" BidderUnruly BidderName = "unruly" BidderVerizonMedia BidderName = "verizonmedia" BidderVisx BidderName = "visx" @@ -125,6 +126,7 @@ var BidderMap = map[string]BidderName{ "tappx": BidderTappx, "triplelift": BidderTriplelift, "triplelift_native": BidderTripleliftNative, + "ucfunnel": BidderUcfunnel, "unruly": BidderUnruly, "verizonmedia": BidderVerizonMedia, "visx": BidderVisx, diff --git a/openrtb_ext/imp_ucfunnel.go b/openrtb_ext/imp_ucfunnel.go new file mode 100644 index 00000000000..408c1e0a35e --- /dev/null +++ b/openrtb_ext/imp_ucfunnel.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpUcfunnel defines the contract for bidrequest.imp[i].ext.ucfunnel +type ExtImpUcfunnel struct { + AdUnitId string `json:"adunitid"` + PartnerId string `json:"partnerid"` +} diff --git a/static/bidder-info/ucfunnel.yaml b/static/bidder-info/ucfunnel.yaml new file mode 100644 index 00000000000..288b0b3f1b8 --- /dev/null +++ b/static/bidder-info/ucfunnel.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "support@ucfunnel.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/ucfunnel.json b/static/bidder-params/ucfunnel.json new file mode 100644 index 00000000000..d39d006cf1f --- /dev/null +++ b/static/bidder-params/ucfunnel.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Ucfunnel Adapter Params", + "description": "A schema which validates params accepted by the Ucfunnel adapter", + "type": "object", + "properties": { + "adunitid": { + "type": "string", + "description": "ID for ad unit" + }, + "partnerid": { + "type": "string", + "description": "ID for partner" + } + }, + "required": ["partnerid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7f65c7f476f..eb25171854a 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -48,6 +48,7 @@ import ( "github.com/prebid/prebid-server/adapters/synacormedia" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" + "github.com/prebid/prebid-server/adapters/ucfunnel" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" @@ -107,6 +108,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 2a8d1fd1b0b..dc224fe99bf 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -57,6 +57,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, + string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, From 199d1dcaecc3685596c6a31bf02631fc97bc8a37 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Tue, 10 Mar 2020 01:04:30 +0300 Subject: [PATCH 024/603] Update required params for TheMediaGrid adapter (#1188) --- adapters/grid/grid.go | 44 ++++++++++++++++++- .../gridtest/exemplary/simple-banner.json | 8 +++- .../grid/gridtest/exemplary/simple-video.json | 8 +++- .../grid/gridtest/params/race/banner.json | 5 ++- .../supplemental/bad_bidder_request.json | 33 ++++++++++++++ .../supplemental/bad_ext_request.json | 30 +++++++++++++ .../gridtest/supplemental/bad_response.json | 2 + .../supplemental/empty_uid_request.json | 33 ++++++++++++++ .../gridtest/supplemental/no_imp_request.json | 13 ++++++ .../gridtest/supplemental/status_204.json | 2 + .../gridtest/supplemental/status_400.json | 2 + .../gridtest/supplemental/status_418.json | 2 + openrtb_ext/imp_grid.go | 6 +++ static/bidder-params/grid.json | 7 ++- 14 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 adapters/grid/gridtest/supplemental/bad_bidder_request.json create mode 100644 adapters/grid/gridtest/supplemental/bad_ext_request.json create mode 100644 adapters/grid/gridtest/supplemental/empty_uid_request.json create mode 100644 adapters/grid/gridtest/supplemental/no_imp_request.json create mode 100644 openrtb_ext/imp_grid.go diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index 3e38edd4578..dd18d52d95a 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -15,11 +15,53 @@ type GridAdapter struct { endpoint string } +func processImp(imp *openrtb.Imp) error { + // get the grid extension + var ext adapters.ExtImpBidder + var gridExt openrtb_ext.ExtImpGrid + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return err + } + if err := json.Unmarshal(ext.Bidder, &gridExt); err != nil { + return err + } + + if gridExt.Uid == 0 { + err := &errortypes.BadInput{ + Message: "uid is empty", + } + return err + } + // no error + return nil +} + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *GridAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) - reqJSON, err := json.Marshal(request) + // copy the request, because we are going to mutate it + requestCopy := *request + // this will contain all the valid impressions + var validImps []openrtb.Imp + // pre-process the imps + for _, imp := range requestCopy.Imp { + if err := processImp(&imp); err == nil { + validImps = append(validImps, imp) + } else { + errors = append(errors, err) + } + } + if len(validImps) == 0 { + err := &errortypes.BadInput{ + Message: "No valid impressions for grid", + } + errors = append(errors, err) + return nil, errors + } + requestCopy.Imp = validImps + + reqJSON, err := json.Marshal(requestCopy) if err != nil { errors = append(errors, err) return nil, errors diff --git a/adapters/grid/gridtest/exemplary/simple-banner.json b/adapters/grid/gridtest/exemplary/simple-banner.json index b098a94f9ba..1a5ea014d0f 100644 --- a/adapters/grid/gridtest/exemplary/simple-banner.json +++ b/adapters/grid/gridtest/exemplary/simple-banner.json @@ -13,7 +13,9 @@ }] }, "ext": { - "bidder": {} + "bidder": { + "uid": 1 + } } }] }, @@ -35,7 +37,9 @@ }] }, "ext": { - "bidder": {} + "bidder": { + "uid": 1 + } } }] } diff --git a/adapters/grid/gridtest/exemplary/simple-video.json b/adapters/grid/gridtest/exemplary/simple-video.json index fcf783da2a4..12c3771d1b2 100644 --- a/adapters/grid/gridtest/exemplary/simple-video.json +++ b/adapters/grid/gridtest/exemplary/simple-video.json @@ -13,7 +13,9 @@ "h": 250 }, "ext": { - "bidder": {} + "bidder": { + "uid": 1 + } } }] }, @@ -35,7 +37,9 @@ "h": 250 }, "ext": { - "bidder": {} + "bidder": { + "uid": 1 + } } }] } diff --git a/adapters/grid/gridtest/params/race/banner.json b/adapters/grid/gridtest/params/race/banner.json index 0967ef424bc..7e347f11b45 100644 --- a/adapters/grid/gridtest/params/race/banner.json +++ b/adapters/grid/gridtest/params/race/banner.json @@ -1 +1,4 @@ -{} +{ + "uid": 1 +} + diff --git a/adapters/grid/gridtest/supplemental/bad_bidder_request.json b/adapters/grid/gridtest/supplemental/bad_bidder_request.json new file mode 100644 index 00000000000..347a3091a41 --- /dev/null +++ b/adapters/grid/gridtest/supplemental/bad_bidder_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": "some not exist" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpGrid", + "comparison": "literal" + }, + { + "value": "No valid impressions for grid", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/grid/gridtest/supplemental/bad_ext_request.json b/adapters/grid/gridtest/supplemental/bad_ext_request.json new file mode 100644 index 00000000000..789db8504f8 --- /dev/null +++ b/adapters/grid/gridtest/supplemental/bad_ext_request.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": "any" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + }, + { + "value": "No valid impressions for grid", + "comparison": "literal" + } + ], + "httpCalls": [] +} diff --git a/adapters/grid/gridtest/supplemental/bad_response.json b/adapters/grid/gridtest/supplemental/bad_response.json index 4ad5c09cf37..87436da7fc1 100644 --- a/adapters/grid/gridtest/supplemental/bad_response.json +++ b/adapters/grid/gridtest/supplemental/bad_response.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } @@ -39,6 +40,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } diff --git a/adapters/grid/gridtest/supplemental/empty_uid_request.json b/adapters/grid/gridtest/supplemental/empty_uid_request.json new file mode 100644 index 00000000000..ff389899788 --- /dev/null +++ b/adapters/grid/gridtest/supplemental/empty_uid_request.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "uid is empty", + "comparison": "literal" + }, + { + "value": "No valid impressions for grid", + "comparison": "literal" + } + ], + "httpCalls":[], + "expectedBidResponses": [] +} diff --git a/adapters/grid/gridtest/supplemental/no_imp_request.json b/adapters/grid/gridtest/supplemental/no_imp_request.json new file mode 100644 index 00000000000..5e261647fb5 --- /dev/null +++ b/adapters/grid/gridtest/supplemental/no_imp_request.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No valid impressions for grid", + "comparison": "literal" + } + ], + "httpCalls":[] +} diff --git a/adapters/grid/gridtest/supplemental/status_204.json b/adapters/grid/gridtest/supplemental/status_204.json index 906d8553bc6..f935cbe85ae 100644 --- a/adapters/grid/gridtest/supplemental/status_204.json +++ b/adapters/grid/gridtest/supplemental/status_204.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } @@ -39,6 +40,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } diff --git a/adapters/grid/gridtest/supplemental/status_400.json b/adapters/grid/gridtest/supplemental/status_400.json index dbf2a4d7b2b..629b1c07bd7 100644 --- a/adapters/grid/gridtest/supplemental/status_400.json +++ b/adapters/grid/gridtest/supplemental/status_400.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } @@ -39,6 +40,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } diff --git a/adapters/grid/gridtest/supplemental/status_418.json b/adapters/grid/gridtest/supplemental/status_418.json index 7619cd6aec1..0ca365c76ce 100644 --- a/adapters/grid/gridtest/supplemental/status_418.json +++ b/adapters/grid/gridtest/supplemental/status_418.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } @@ -39,6 +40,7 @@ }, "ext": { "bidder": { + "uid": 1 } } } diff --git a/openrtb_ext/imp_grid.go b/openrtb_ext/imp_grid.go new file mode 100644 index 00000000000..d38e610d7a5 --- /dev/null +++ b/openrtb_ext/imp_grid.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpGrid defines the contract for bidrequest.imp[i].ext.grid +type ExtImpGrid struct { + Uid int `json:"uid"` +} diff --git a/static/bidder-params/grid.json b/static/bidder-params/grid.json index 7a0cf3da8c5..67f9b12f115 100644 --- a/static/bidder-params/grid.json +++ b/static/bidder-params/grid.json @@ -3,6 +3,11 @@ "title": "TheMediaGrid Adapter Params", "description": "A schema which validates params accepted by TheMediaGrid adapter", "type": "object", - "properties": {}, + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, "required": [] } From 5a9da6672f5c51b4c90b8d0feb4e7649d039bf1e Mon Sep 17 00:00:00 2001 From: htang555 Date: Thu, 12 Mar 2020 10:43:41 -0700 Subject: [PATCH 025/603] add zeroclickfraud adapter (#1207) * add zeroclickfraud adapter * fixes for PR * fix casing of Zeroclickfraud --- adapters/zeroclickfraud/usersync.go | 12 ++ adapters/zeroclickfraud/usersync_test.go | 34 ++++ adapters/zeroclickfraud/zeroclickfraud.go | 187 ++++++++++++++++++ .../zeroclickfraud/zeroclickfraud_test.go | 11 ++ .../exemplary/multi-request.json | 160 +++++++++++++++ .../zeroclickfraudtest/exemplary/native.json | 123 ++++++++++++ .../exemplary/simple-banner.json | 133 +++++++++++++ .../exemplary/simple-video.json | 138 +++++++++++++ .../params/race/banner.json | 4 + .../params/race/native.json | 4 + .../zeroclickfraudtest/params/race/video.json | 4 + .../supplemental/bad-host.json | 33 ++++ .../supplemental/bad-response-body.json | 88 +++++++++ .../supplemental/bad-server-response.json | 88 +++++++++ .../supplemental/bad-sourceId.json | 35 ++++ .../supplemental/missing-ext.json | 27 +++ .../supplemental/missing-extparam.json | 30 +++ .../supplemental/no-content-response.json | 82 ++++++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_zeroclickfraud.go | 7 + static/bidder-info/zeroclickfraud.yaml | 13 ++ static/bidder-params/zeroclickfraud.json | 19 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 1241 insertions(+) create mode 100644 adapters/zeroclickfraud/usersync.go create mode 100644 adapters/zeroclickfraud/usersync_test.go create mode 100644 adapters/zeroclickfraud/zeroclickfraud.go create mode 100644 adapters/zeroclickfraud/zeroclickfraud_test.go create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json create mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json create mode 100644 openrtb_ext/imp_zeroclickfraud.go create mode 100644 static/bidder-info/zeroclickfraud.yaml create mode 100644 static/bidder-params/zeroclickfraud.json diff --git a/adapters/zeroclickfraud/usersync.go b/adapters/zeroclickfraud/usersync.go new file mode 100644 index 00000000000..833524e4b3e --- /dev/null +++ b/adapters/zeroclickfraud/usersync.go @@ -0,0 +1,12 @@ +package zeroclickfraud + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("zeroclickfraud", 0, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/zeroclickfraud/usersync_test.go b/adapters/zeroclickfraud/usersync_test.go new file mode 100644 index 00000000000..30ade771a4c --- /dev/null +++ b/adapters/zeroclickfraud/usersync_test.go @@ -0,0 +1,34 @@ +package zeroclickfraud + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestZeroClickFraudSyncer(t *testing.T) { + syncURL := "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewZeroClickFraudSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + }, + CCPA: ccpa.Policy{ + Value: "1NYN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://s.0cf.io/sync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go new file mode 100644 index 00000000000..963074bf637 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -0,0 +1,187 @@ +package zeroclickfraud + +import ( + "encoding/json" + "fmt" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "text/template" +) + +type ZeroClickFraudAdapter struct { + EndpointTemplate template.Template +} + +func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + errs := make([]error, 0, len(request.Imp)) + headers := http.Header{ + "Content-Type": {"application/json"}, + "Accept": {"application/json"}, + } + + // Pull the host and source ID info from the bidder params. + reqImps, err := splitImpressions(request.Imp) + + if err != nil { + errs = append(errs, err) + } + + requests := []*adapters.RequestData{} + + for reqExt, reqImp := range reqImps { + request.Imp = reqImp + reqJson, err := json.Marshal(request) + + if err != nil { + errs = append(errs, err) + continue + } + + urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)} + url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams) + + if err != nil { + errs = append(errs, err) + continue + } + + request := adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJson, + Headers: headers} + + requests = append(requests, &request) + } + + return requests, errs +} + +/* +internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) +*/ +func (a *ZeroClickFraudAdapter) MakeBids( + internalRequest *openrtb.BidRequest, + externalRequest *adapters.RequestData, + response *adapters.ResponseData, +) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("ERR, bad input %d", response.StatusCode), + }} + } else if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponse() + bidResponse.Currency = bidResp.Cur + + for _, seatBid := range bidResp.SeatBid { + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaType(bid.ImpID, internalRequest.Imp), + }) + } + } + + return bidResponse, nil +} + +func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp, error) { + + var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp) + + for _, imp := range imps { + bidderParams, err := getBidderParams(&imp) + if err != nil { + return nil, err + } + + m[*bidderParams] = append(m[*bidderParams], imp) + } + + return m, nil +} + +func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()), + } + } + var zeroclickfraudExt openrtb_ext.ExtImpZeroClickFraud + if err := json.Unmarshal(bidderExt.Bidder, &zeroclickfraudExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()), + } + } + + if zeroclickfraudExt.SourceId < 1 { + return nil, &errortypes.BadInput{ + Message: "Invalid/Missing SourceId", + } + } + + if len(zeroclickfraudExt.Host) < 1 { + return nil, &errortypes.BadInput{ + Message: "Invalid/Missing Host", + } + } + + return &zeroclickfraudExt, nil +} + +func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + + bidType := openrtb_ext.BidTypeBanner + + for _, imp := range imps { + if imp.ID == impID { + if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + break + } else if imp.Native != nil { + bidType = openrtb_ext.BidTypeNative + break + } else { + bidType = openrtb_ext.BidTypeBanner + break + } + } + } + + return bidType +} + +func NewZeroClickFraudBidder(endpoint string) *ZeroClickFraudAdapter { + template, err := template.New("endpointTemplate").Parse(endpoint) + if err != nil { + glog.Fatal("Unable to parse endpoint url template") + return nil + } + + return &ZeroClickFraudAdapter{EndpointTemplate: *template} +} diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go new file mode 100644 index 00000000000..ebe41c19d2e --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -0,0 +1,11 @@ +package zeroclickfraud + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "zeroclickfraudtest", NewZeroClickFraudBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}")) +} diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json new file mode 100644 index 00000000000..70bfb9645c8 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + },{ + "id": "some-impression-id2", + "banner": + { + "format": [{ + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + },{ + "id": "some-impression-id2", + "banner": + { + "format": [ + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-5-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json new file mode 100644 index 00000000000..dcf9064f29d --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "native": + { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}", + "ver": "1.1" + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "user": + { + "buyeruid": "4610943261" + }, + "at": 1, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "native": + { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}", + "ver": "1.1" + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "user": + { + "buyeruid": "4610943261" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status":200, + "body": { + "id": "some-request-id", + "bidid": "183975330-3-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314346", + "impid": "some-impression-id", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://t.0cf.io/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://t.0cf.io/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "ext": {} + }] + }], + "cur": "USD", + "ext": {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "2181314346", + "impid": "some-impression-id", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://t.0cf.io/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://t.0cf.io/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "ext": {} + }, + "type":"native" + } + ] + }] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..1d5ee3b3a52 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-5-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json new file mode 100644 index 00000000000..949e74602dd --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": + { + "mimes":[ + "video/x-flv" + ], + "w": 500, + "h": 400, + "minduration": 30 + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": + { + "mimes":[ + "video/x-flv" + ], + "w": 500, + "h": 400, + "minduration": 30 + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-4-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314347", + "impid": "some-impression-id", + "nurl": "https://t.0cf.io/wm/267237/?fcid=2181314347", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906729", + "w": 500, + "h": 400, + "ext": + { + "type": "CPM" + } + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314347", + "impid": "some-impression-id", + "price": 13.37, + "nurl": "https://t.0cf.io/wm/267237/?fcid=2181314347", + "adid": "906297", + "cid": "906293", + "crid": "906729", + "w": 500, + "h": 400, + "ext": + { + "type": "CPM" + } + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json new file mode 100644 index 00000000000..cff0af83143 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "q.0cf.io" +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json new file mode 100644 index 00000000000..cff0af83143 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "q.0cf.io" +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json new file mode 100644 index 00000000000..cff0af83143 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "q.0cf.io" +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json new file mode 100644 index 00000000000..cee5efbe760 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "bad-host-test", + "imp": [ + { + "id": "bad-host-test-imp", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": + { + "bidder": + { + "host": "", + "sourceId": 123 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid/Missing Host", + "comparison": "literal" + } + ] +} diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json new file mode 100644 index 00000000000..84d6bd9d889 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body":"foobar" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..fdea4f109a7 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 500, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "ERR, response with status 500", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json new file mode 100644 index 00000000000..4d86c32cd58 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "bad-sourceId-test", + "imp": [ + { + "id": "bad-sourceId-test-imp", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 0 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid/Missing SourceId", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json new file mode 100644 index 00000000000..68d29e880b9 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "missing-extbid-test", + "imp": [ + { + "id": "missing-extbid-test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Missing bidder ext: unexpected end of JSON input", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json new file mode 100644 index 00000000000..d272cd5347c --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "missing-extbid-test", + "imp": [ + { + "id": "missing-extbid-test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "sourceId":54326 + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Cannot Resolve host or sourceId: unexpected end of JSON input", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json new file mode 100644 index 00000000000..3a36d6e04b2 --- /dev/null +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://q.0cf.io/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "q.0cf.io", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 204 + } + }], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index b9501cf6355..953854bf8de 100644 --- a/config/config.go +++ b/config/config.go @@ -534,6 +534,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") } func setDefaultUsersync(m map[string]Adapter, bidder openrtb_ext.BidderName, defaultValue string) { @@ -726,6 +727,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") + v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) v.SetDefault("analytics.file.filename", "") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index f0ff9c5b1a7..0354f258158 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -61,6 +61,7 @@ import ( "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yieldmo" + "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -130,6 +131,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), openrtb_ext.BidderYieldmo: yieldmo.NewYieldmoBidder(cfg.Adapters[string(openrtb_ext.BidderYieldmo)].Endpoint), + openrtb_ext.BidderZeroClickFraud: zeroclickfraud.NewZeroClickFraudBidder(cfg.Adapters[string(openrtb_ext.BidderZeroClickFraud)].Endpoint), } legacyBidders := map[openrtb_ext.BidderName]adapters.Adapter{ diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index d1490603b50..ed3d20e06ab 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -74,6 +74,7 @@ const ( BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYieldmo BidderName = "yieldmo" + BidderZeroClickFraud BidderName = "zeroclickfraud" ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. @@ -132,6 +133,7 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "vrtcal": BidderVrtcal, "yieldmo": BidderYieldmo, + "zeroclickfraud": BidderZeroClickFraud, } // BidderList returns the values of the BidderMap diff --git a/openrtb_ext/imp_zeroclickfraud.go b/openrtb_ext/imp_zeroclickfraud.go new file mode 100644 index 00000000000..ae82fcacd9a --- /dev/null +++ b/openrtb_ext/imp_zeroclickfraud.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpZeroClickFraud defines the contract for bidrequest.imp[i].ext.datablocks +type ExtImpZeroClickFraud struct { + SourceId int `json:"sourceId"` + Host string `json:"host"` +} diff --git a/static/bidder-info/zeroclickfraud.yaml b/static/bidder-info/zeroclickfraud.yaml new file mode 100644 index 00000000000..9bf7e780914 --- /dev/null +++ b/static/bidder-info/zeroclickfraud.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "henry@datablocks.net" +capabilities: + app: + mediaTypes: + - banner + - native + - video + site: + mediaTypes: + - banner + - native + - video diff --git a/static/bidder-params/zeroclickfraud.json b/static/bidder-params/zeroclickfraud.json new file mode 100644 index 00000000000..1c5e3c633b4 --- /dev/null +++ b/static/bidder-params/zeroclickfraud.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ZeroClickFraud Adapter Params", + "description": "A schema which validates params accepted by the ZeroClickFraud adapter", + + "type": "object", + "properties": { + "sourceId": { + "type": "integer", + "minimum": 1, + "description": "Website Source Id" + }, + "host": { + "type": "string", + "description": "Network Host to request from" + } + }, + "required": ["host", "sourceId"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index eb25171854a..c58d552844d 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -54,6 +54,7 @@ import ( "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yieldmo" + "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" @@ -114,6 +115,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) return syncers } diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index dc224fe99bf..cc6d4b5870a 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -63,6 +63,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, string(openrtb_ext.BidderYieldmo): syncConfig, + string(openrtb_ext.BidderZeroClickFraud): syncConfig, }, } From 8668dfca16538d894355f67d99257a331fdaea45 Mon Sep 17 00:00:00 2001 From: vstatkevich Date: Thu, 12 Mar 2020 20:44:04 +0300 Subject: [PATCH 026/603] Fix Adform's parameters regex (#1214) * Added adform info file * Added Adform adapter and bidder * Updates from master * Removed usersyncInfo from Adform adapter. Inverted Imp type check. * Removed excessive loop * Updated with the last master * Create readme file for adform * Fix Adform's parameters regex Motivation: catastrophic backtracking during regex execution Details: - https://regex101.com/r/NNQrWq/1 - string to check "url_domain:keskustelu.suomi24.fi,url_path:/matkailu/matkakohteet/aasia,layout:lg,categories:Matkailu,main_category:Matkailu" Co-authored-by: v.statkevich Co-authored-by: Olga Linkevich --- static/bidder-params/adform.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json index 308ae3e9414..67f09623ee4 100644 --- a/static/bidder-params/adform.json +++ b/static/bidder-params/adform.json @@ -16,7 +16,7 @@ "mkv": { "type": "string", "description": "Comma-separated key-value pairs. Forbidden symbols: &. Example: mkv='color:blue,length:350'", - "pattern": "^(\\s*|(([^,:&]*[^,:&\\s]+[^,:&]*)+:[^,:&]*,)*(([^,:&]*[^,:&\\s]+[^,:&]*)+:[^,:&]*,?))$" + "pattern": "^(\\s*|((\\s*[^,:&\\s]+\\s*:[^,:&]*)(,\\s*[^,:&\\s]+\\s*:[^,:&]*)*))$" }, "mkw": { "type": "string", From c515816e970fe820cbcf6ce665f67befd3bf7529 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 12 Mar 2020 11:18:24 -0700 Subject: [PATCH 027/603] If Device.UA is not present in request body, init it with user-agent from header (#1219) * If Device.UA is not present in request body, init it with user-agent from request header if it's present * Moved User-Agent handler to parseVideoRequest func and added unit test * Minor clean up Co-authored-by: Veronika Solovei --- ...o_valid_sample_with_device_user_agent.json | 80 +++++++++++++++++++ ...alid_sample_without_device_user_agent.json | 63 +++++++++++++++ endpoints/openrtb2/video_auction.go | 9 ++- endpoints/openrtb2/video_auction_test.go | 75 +++++++++++++++++ 4 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json create mode 100644 endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json new file mode 100644 index 00000000000..68c3f4e1c15 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json @@ -0,0 +1,80 @@ + +{ + "accountid": "555888777", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [ + { + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "user": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + }, + "gdpr": { + "consentrequired": false, + "consentstring": "something" + }, + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling" + }, + "device": { + "ua": "TestHeaderSample", + "ip": "123.145.167.10", + "devicetype": 1, + "dnt": 33, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory":{ + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2,3,5,6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 + }, + "cacheconfig": { + "ttl": 42 + } +} diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json new file mode 100644 index 00000000000..e040a5625ba --- /dev/null +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json @@ -0,0 +1,63 @@ + +{ + "accountid": "555888777", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [ + { + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "user": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + }, + "gdpr": { + "consentrequired": false, + "consentstring": "something" + }, + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling" + }, + "includebrandcategory":{ + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2,3,5,6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 + }, + "cacheconfig": { + "ttl": 42 + } +} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index feb8de193e7..2a8663959a6 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -120,7 +120,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } } //unmarshal and validate combined result - videoBidReq, errL, podErrors := deps.parseVideoRequest(resolvedRequest) + videoBidReq, errL, podErrors := deps.parseVideoRequest(resolvedRequest, r.Header) if len(errL) > 0 { handleError(&labels, w, errL, &vo) return @@ -556,7 +556,7 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro return reqJSON, nil } -func (deps *endpointDeps) parseVideoRequest(request []byte) (req *openrtb_ext.BidRequestVideo, errs []error, podErrors []PodError) { +func (deps *endpointDeps) parseVideoRequest(request []byte, headers http.Header) (req *openrtb_ext.BidRequestVideo, errs []error, podErrors []PodError) { req = &openrtb_ext.BidRequestVideo{} if err := json.Unmarshal(request, &req); err != nil { @@ -564,6 +564,11 @@ func (deps *endpointDeps) parseVideoRequest(request []byte) (req *openrtb_ext.Bi return } + //if Device.UA is not present in request body, init it with user-agent from request header if it's present + if req.Device.UA == "" { + req.Device.UA = headers.Get("User-Agent") + } + errL, podErrors := deps.validateVideoRequest(req) if len(errL) > 0 { errs = append(errs, errL...) diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index dfe2a6a50b8..a5ad62c9fa8 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "io/ioutil" + "net/http" "net/http/httptest" "strings" "testing" @@ -745,6 +746,80 @@ func TestHandleErrorMetrics(t *testing.T) { assert.Equal(t, "request missing required field: PodConfig.Pods", mod.videoObjects[0].Errors[1].Error(), "Second error in AnalyticsObject should have message regarding Pods") } +func TestParseVideoRequestWithUserAgentAndHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_with_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + headers := http.Header{} + headers.Add("User-Agent", "TestHeader") + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + +func TestParseVideoRequestWithUserAgentAndEmptyHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_with_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + headers := http.Header{} + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + +func TestParseVideoRequestWithoutUserAgentWithHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_without_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + headers := http.Header{} + headers.Add("User-Agent", "TestHeader") + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, "TestHeader", req.Device.UA, "Device.ua should be taken from request header") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + +func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_without_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + headers := http.Header{} + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, "", req.Device.UA, "Device.ua should be empty") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} From f3787be596f55e546f4656d526b1045dced2c614 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Fri, 13 Mar 2020 14:30:34 -0700 Subject: [PATCH 028/603] Queued request timeout (#1217) Co-authored-by: Veronika Solovei --- config/config.go | 10 ++ router/aspects/request_timeout_handler.go | 43 ++++++ .../aspects/request_timeout_handler_test.go | 124 ++++++++++++++++++ router/router.go | 6 + 4 files changed, 183 insertions(+) create mode 100644 router/aspects/request_timeout_handler.go create mode 100644 router/aspects/request_timeout_handler_test.go diff --git a/config/config.go b/config/config.go index 953854bf8de..e3b6c67b651 100644 --- a/config/config.go +++ b/config/config.go @@ -64,6 +64,8 @@ type Configuration struct { AccountRequired bool `mapstructure:"account_required"` // Local private file containing SSL certificates PemCertsFile string `mapstructure:"certificates_file"` + // Custom headers to handle request timeouts from queueing infrastructure + RequestTimeoutHeaders RequestTimeoutHeaders `mapstructure:"request_timeout_headers"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -199,6 +201,11 @@ type HostCookie struct { TTL int64 `mapstructure:"ttl_days"` } +type RequestTimeoutHeaders struct { + RequestTimeInQueue string `mapstructure:"request_time_in_queue"` + RequestTimeoutInQueue string `mapstructure:"request_timeout_in_queue"` +} + func (cfg *HostCookie) TTLDuration() time.Duration { return time.Duration(cfg.TTL) * time.Hour * 24 } @@ -748,6 +755,9 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("account_required", false) v.SetDefault("certificates_file", "") + v.SetDefault("request_timeout_headers.request_time_in_queue", "") + v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") + // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetEnvPrefix("PBS") diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go new file mode 100644 index 00000000000..ae11f8c5614 --- /dev/null +++ b/router/aspects/request_timeout_handler.go @@ -0,0 +1,43 @@ +package aspects + +import ( + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "net/http" + "strconv" +) + +func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders) httprouter.Handle { + + return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + + reqTimeInQueue := r.Header.Get(reqTimeoutHeaders.RequestTimeInQueue) + reqTimeout := r.Header.Get(reqTimeoutHeaders.RequestTimeoutInQueue) + + //If request timeout headers are not specified - process request as usual + if reqTimeInQueue == "" || reqTimeout == "" { + f(w, r, params) + return + } + + reqTimeFloat, reqTimeFloatErr := strconv.ParseFloat(reqTimeInQueue, 64) + reqTimeoutFloat, reqTimeoutFloatErr := strconv.ParseFloat(reqTimeout, 64) + + //Return HTTP 500 if request timeout headers are incorrect (wrong format) + if reqTimeFloatErr != nil || reqTimeoutFloatErr != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Request timeout headers are incorrect (wrong format)")) + return + } + + //Return HTTP 408 if requests stays too long in queue + if reqTimeFloat >= reqTimeoutFloat { + w.WriteHeader(http.StatusRequestTimeout) + w.Write([]byte("Queued request processing time exceeded maximum")) + return + } + + f(w, r, params) + } + +} diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go new file mode 100644 index 00000000000..b6e10fd64bf --- /dev/null +++ b/router/aspects/request_timeout_handler_test.go @@ -0,0 +1,124 @@ +package aspects + +import ( + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +const reqTimeInQueueHeaderName = "X-Ngx-Request-Time" +const reqTimeoutHeaderName = "X-Request-Timeout" + +func TestAny(t *testing.T) { + testCases := []struct { + reqTimeInQueue string + reqTimeOut string + setHeaders bool + extectedRespCode int + expectedRespCodeMessage string + expectedRespBody string + expectedRespBodyMessage string + }{ + { + //TestQueuedRequestTimeoutWithTimeout + reqTimeInQueue: "6", + reqTimeOut: "5", + setHeaders: true, + extectedRespCode: http.StatusRequestTimeout, + expectedRespCodeMessage: "Http response code is incorrect, should be 408", + expectedRespBody: "Queued request processing time exceeded maximum", + expectedRespBodyMessage: "Body should have error message", + }, + { + //TestQueuedRequestTimeoutNoTimeout + reqTimeInQueue: "0.9", + reqTimeOut: "5", + setHeaders: true, + extectedRespCode: http.StatusOK, + expectedRespCodeMessage: "Http response code is incorrect, should be 200", + expectedRespBody: "Executed", + expectedRespBodyMessage: "Body should be present in response", + }, + { + //TestQueuedRequestNoHeaders + reqTimeInQueue: "", + reqTimeOut: "", + setHeaders: false, + extectedRespCode: http.StatusOK, + expectedRespCodeMessage: "Http response code is incorrect, should be 200", + expectedRespBody: "Executed", + expectedRespBodyMessage: "Body should be present in response", + }, + { + //TestQueuedRequestSomeHeaders + reqTimeInQueue: "2", + reqTimeOut: "", + setHeaders: true, + extectedRespCode: http.StatusOK, + expectedRespCodeMessage: "Http response code is incorrect, should be 200", + expectedRespBody: "Executed", + expectedRespBodyMessage: "Body should be present in response", + }, + { + //TestQueuedRequestAllHeadersIncorrect + reqTimeInQueue: "test1", + reqTimeOut: "test2", + setHeaders: true, + extectedRespCode: http.StatusInternalServerError, + expectedRespCodeMessage: "Http response code is incorrect, should be 400", + expectedRespBody: "Request timeout headers are incorrect (wrong format)", + expectedRespBodyMessage: "Body should have error message", + }, + { + //TestQueuedRequestSomeHeadersIncorrect + reqTimeInQueue: "test1", + reqTimeOut: "123", + setHeaders: true, + extectedRespCode: http.StatusInternalServerError, + expectedRespCodeMessage: "Http response code is incorrect, should be 400", + expectedRespBody: "Request timeout headers are incorrect (wrong format)", + expectedRespBodyMessage: "Body should have error message", + }, + } + + for _, test := range testCases { + result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders) + assert.Equal(t, test.extectedRespCode, result.Code, test.expectedRespCodeMessage) + assert.Equal(t, test.expectedRespBody, string(result.Body.Bytes()), test.expectedRespBodyMessage) + } +} + +func MockEndpoint() httprouter.Handle { + return httprouter.Handle(MockHandler) +} + +func MockHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.Write([]byte("Executed")) +} + +func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, setHeaders bool) *httptest.ResponseRecorder { + rw := httptest.NewRecorder() + req, err := http.NewRequest("POST", "/test", nil) + if err != nil { + assert.Fail(t, "Unable create mock http request") + } + if setHeaders { + req.Header.Set(reqTimeInQueueHeaderName, timeInQueue) + req.Header.Set(reqTimeoutHeaderName, reqTimeout) + } + + customHeaders := config.RequestTimeoutHeaders{reqTimeInQueueHeaderName, reqTimeoutHeaderName} + + handler := QueuedRequestTimeout(MockEndpoint(), customHeaders) + + r := httprouter.New() + r.POST("/test", handler) + + r.ServeHTTP(rw, req) + + return rw +} diff --git a/router/router.go b/router/router.go index 449ab65a448..7e713ca637a 100644 --- a/router/router.go +++ b/router/router.go @@ -38,6 +38,7 @@ import ( "github.com/prebid/prebid-server/pbs" metricsConf "github.com/prebid/prebid-server/pbsmetrics/config" pbc "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/router/aspects" "github.com/prebid/prebid-server/ssl" storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync/usersyncers" @@ -255,6 +256,11 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r glog.Fatalf("Failed to create the video endpoint handler. %v", err) } + requestTimeoutHeaders := config.RequestTimeoutHeaders{} + if cfg.RequestTimeoutHeaders != requestTimeoutHeaders { + videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders) + } + r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges)) r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) From e94ca8b7e635d599a6e30fb73cc776d704afbf24 Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 16 Mar 2020 12:53:09 -0400 Subject: [PATCH 029/603] docs: adding currency support section (#1199) --- docs/endpoints/openrtb2/auction.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index 9ae6ec78bee..d670b092174 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -403,6 +403,29 @@ Example: PBS receiving a request for an interstitial imp and these parameters set, it will rewrite the format object within the interstitial imp. If the format array's first object is a size, PBS will take it as the max size for the interstitial. If that size is 1x1, it will look up the device's size and use that as the max size. If the format is not present, it will also use the device size as the max size. (1x1 support so that you don't have to omit the format object to use the device size) PBS with interstitial support will come preconfigured with a list of common ad sizes. Preferentially organized by weighing the larger and more common sizes first. But no guarantees to the ordering will be made. PBS will generate a new format list for the interstitial imp by traversing this list and picking the first 10 sizes that fall within the imp's max size and minimum percentage size. There will be no attempt to favor aspect ratios closer to the original size's aspect ratio. The limit of 10 is enforced to ensure we don't overload bidders with an overlong list. All the interstitial parameters will still be passed to the bidders, so they may recognize them and use their own size matching algorithms if they prefer. +#### Currency Support + +To set the desired 'ad server currency', use the standard OpenRTB `cur` attribute. Note that Prebid Server only looks at the first currency in the array. +``` +"cur": ["USD"] +``` + +If you want or need to define currency conversion rates (e.g. for currencies that your Prebid Server doesn't support), define ext.prebid.currency.rates. (Currently supported in PBS-Java only) + +``` +"ext": { + "prebid": { + "currency": { + "rates": { + "USD": { "UAH": 24.47, "ETB": 32.04 } + } + } + } +} +``` + +If it exists, a rate defined in ext.prebid.currency.rates has the highest priority. If a currency rate doesn't exist in the request, the external file will be used. + #### Stored Responses (PBS-Java only) While testing SDK and video integrations, it's important, but often difficult, to get consistent responses back from bidders that cover a range of scenarios like different CPM values, deals, etc. Prebid Server supports a debugging workflow in two ways: From 2bad06903f480e6117a7905f916d59f3aadafab8 Mon Sep 17 00:00:00 2001 From: thuyhq <61451682+thuyhq@users.noreply.github.com> Date: Mon, 16 Mar 2020 23:54:59 +0700 Subject: [PATCH 030/603] Add ValueImpression Adapter (#1204) --- adapters/valueimpression/params_test.go | 52 ++++++ adapters/valueimpression/usersync.go | 12 ++ adapters/valueimpression/usersync_test.go | 35 ++++ adapters/valueimpression/valueimpression.go | 154 ++++++++++++++++++ .../valueimpression/valueimpression_test.go | 11 ++ .../exemplary/banner-and-video.json | 150 +++++++++++++++++ .../valueimpressiontest/exemplary/banner.json | 98 +++++++++++ .../valueimpressiontest/exemplary/video.json | 53 ++++++ .../supplemental/explicit-dimensions.json | 56 +++++++ .../invalid-response-no-bids.json | 50 ++++++ .../invalid-response-unmarshall-error.json | 66 ++++++++ .../supplemental/no-imps-in-request.json | 18 ++ .../supplemental/server-error-code.json | 53 ++++++ .../supplemental/server-no-content.json | 45 +++++ .../supplemental/wrong-impression-ext.json | 26 +++ .../wrong-impression-mapping.json | 75 +++++++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_valueimpression.go | 5 + static/bidder-info/valueimpression.yaml | 11 ++ static/bidder-params/valueimpression.json | 15 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 24 files changed, 994 insertions(+) create mode 100644 adapters/valueimpression/params_test.go create mode 100644 adapters/valueimpression/usersync.go create mode 100644 adapters/valueimpression/usersync_test.go create mode 100644 adapters/valueimpression/valueimpression.go create mode 100644 adapters/valueimpression/valueimpression_test.go create mode 100644 adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json create mode 100644 adapters/valueimpression/valueimpressiontest/exemplary/banner.json create mode 100644 adapters/valueimpression/valueimpressiontest/exemplary/video.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json create mode 100644 adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json create mode 100644 openrtb_ext/imp_valueimpression.go create mode 100644 static/bidder-info/valueimpression.yaml create mode 100644 static/bidder-params/valueimpression.json diff --git a/adapters/valueimpression/params_test.go b/adapters/valueimpression/params_test.go new file mode 100644 index 00000000000..46471de24bb --- /dev/null +++ b/adapters/valueimpression/params_test.go @@ -0,0 +1,52 @@ +package valueimpression + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/valueimpression.json +// These also validate the format of the external API: request.imp[i].ext.valueimpression +// TestValidParams makes sure that the ValueImpression schema accepts all imp.ext fields which we intend to support. + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderValueImpression, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected ValueImpression params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the ValueImpression schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderValueImpression, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"siteId": "123"}`, +} + +var invalidParams = []string{ + `{}`, + `null`, + `true`, + `154`, + `{"siteId": 123}`, // siteId should be string + `{"invalid_param": "123"}`, +} diff --git a/adapters/valueimpression/usersync.go b/adapters/valueimpression/usersync.go new file mode 100644 index 00000000000..34addbc0e75 --- /dev/null +++ b/adapters/valueimpression/usersync.go @@ -0,0 +1,12 @@ +package valueimpression + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewValueImpressionSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("valueimpression", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go new file mode 100644 index 00000000000..63f123055a9 --- /dev/null +++ b/adapters/valueimpression/usersync_test.go @@ -0,0 +1,35 @@ +package valueimpression + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestValueImpressionSyncer(t *testing.T) { + syncURL := "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewValueImpressionSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", + }, + CCPA: ccpa.Policy{ + Value: "1NYN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://rtb.valueimpression.com/usersync?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.False(t, syncInfo.SupportCORS) +} diff --git a/adapters/valueimpression/valueimpression.go b/adapters/valueimpression/valueimpression.go new file mode 100644 index 00000000000..7e0f5f28cb9 --- /dev/null +++ b/adapters/valueimpression/valueimpression.go @@ -0,0 +1,154 @@ +package valueimpression + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type ValueImpressionAdapter struct { + endpoint string +} + +func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var adapterRequests []*adapters.RequestData + + if err := preprocess(request); err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterReq, err := a.makeRequest(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterRequests = append(adapterRequests, adapterReq) + + return adapterRequests, errs +} + +func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { + var err error + + jsonBody, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: jsonBody, + Headers: headers, + }, nil +} + +func preprocess(request *openrtb.BidRequest) error { + if len(request.Imp) == 0 { + return &errortypes.BadInput{ + Message: "No Imps in Bid Request", + } + } + for i := 0; i < len(request.Imp); i++ { + var imp = &request.Imp[i] + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + var extImp openrtb_ext.ExtImpValueImpression + if err := json.Unmarshal(bidderExt.Bidder, &extImp); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + imp.Ext = bidderExt.Bidder + } + + return nil +} + +// MakeBids based on valueimpression server response +func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", responseData.StatusCode), + }} + } + + if responseData.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", responseData.StatusCode), + }} + } + + var bidResponse openrtb.BidResponse + + if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + if len(bidResponse.SeatBid) == 0 { + return nil, nil + } + + rv := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid)) + var errors []error + + for _, seatbid := range bidResponse.SeatBid { + for _, bid := range seatbid.Bid { + foundMatchingBid := false + bidType := openrtb_ext.BidTypeBanner + for _, imp := range bidRequest.Imp { + if imp.ID == bid.ImpID { + foundMatchingBid = true + if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + } + break + } + } + + if foundMatchingBid { + rv.Bids = append(rv.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } else { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid id='%s' could not find valid impid='%s'", bid.ID, bid.ImpID), + }) + } + } + } + return rv, errors +} + +func NewValueImpressionBidder(endpoint string) *ValueImpressionAdapter { + return &ValueImpressionAdapter{ + endpoint: endpoint, + } +} diff --git a/adapters/valueimpression/valueimpression_test.go b/adapters/valueimpression/valueimpression_test.go new file mode 100644 index 00000000000..047521cea41 --- /dev/null +++ b/adapters/valueimpression/valueimpression_test.go @@ -0,0 +1,11 @@ +package valueimpression + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "valueimpressiontest", NewValueImpressionBidder("//host")) +} diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..107c0d84221 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + }, + { + "id": "test-video-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ], + "site": { + "id": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "siteId": "123" + } + }, + { + "id": "test-video-imp-id", + "video": { + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640 + }, + "ext": { + "siteId": "123" + } + } + ], + "site": { + "id": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "valueimpression", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-video-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "banner" + }, + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29484110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29484110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/banner.json b/adapters/valueimpression/valueimpressiontest/exemplary/banner.json new file mode 100644 index 00000000000..1ef11ade199 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/exemplary/banner.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "siteId": "123" + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "valueimpression", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/video.json b/adapters/valueimpression/valueimpressiontest/exemplary/video.json new file mode 100644 index 00000000000..c6e71e7a16f --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/exemplary/video.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json new file mode 100644 index 00000000000..ee23350c9dc --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json new file mode 100644 index 00000000000..114b27bae07 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + ], + "cur": "USD" + } + } + } + ] +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json new file mode 100644 index 00000000000..c854548b78b --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + { + "bid": [ + { + "id": "uuid", + "impid": "some_test_ad", + "w": "728", + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json b/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json new file mode 100644 index 00000000000..274a34227cf --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "No Imps in Bid Request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json b/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json new file mode 100644 index 00000000000..ea31fdc2fe9 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: HTTP status 500", + "comparison": "literal" + } + ] + } diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json b/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json new file mode 100644 index 00000000000..85633201bc4 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] + } diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json new file mode 100644 index 00000000000..13514ac8ab8 --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "rqid", + "imp": [ + { + "id": "impid", + "video": { + "w": 100, + "h": 200 + }, + "ext": { + "bidder": { + "siteId": 123 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field ExtImpValueImpression.siteId of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..ef4d3a7526b --- /dev/null +++ b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "siteId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "siteId": "123" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "BOGUS-IMPID", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid id='test-bid-id' could not find valid impid='BOGUS-IMPID'", + "comparison": "regex" + } +] +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index e3b6c67b651..2069730b692 100644 --- a/config/config.go +++ b/config/config.go @@ -538,6 +538,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -730,6 +731,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20") v.SetDefault("adapters.ucfunnel.endpoint", "http://apac-hk-adx.aralego.com/prebid") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") + v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 0354f258158..7b841a2838e 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -57,6 +57,7 @@ import ( "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" "github.com/prebid/prebid-server/adapters/unruly" + "github.com/prebid/prebid-server/adapters/valueimpression" "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" @@ -127,6 +128,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderTripleliftNative: triplelift_native.NewTripleliftNativeBidder(client, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].ExtraAdapterInfo), openrtb_ext.BidderUcfunnel: ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint), openrtb_ext.BidderUnruly: unruly.NewUnrulyBidder(client, cfg.Adapters[string(openrtb_ext.BidderUnruly)].Endpoint), + openrtb_ext.BidderValueImpression: valueimpression.NewValueImpressionBidder(cfg.Adapters[string(openrtb_ext.BidderValueImpression)].Endpoint), openrtb_ext.BidderVerizonMedia: verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint), openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ed3d20e06ab..627842f57ff 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -70,6 +70,7 @@ const ( BidderTripleliftNative BidderName = "triplelift_native" BidderUcfunnel BidderName = "ucfunnel" BidderUnruly BidderName = "unruly" + BidderValueImpression BidderName = "valueimpression" BidderVerizonMedia BidderName = "verizonmedia" BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" @@ -129,6 +130,7 @@ var BidderMap = map[string]BidderName{ "triplelift_native": BidderTripleliftNative, "ucfunnel": BidderUcfunnel, "unruly": BidderUnruly, + "valueimpression": BidderValueImpression, "verizonmedia": BidderVerizonMedia, "visx": BidderVisx, "vrtcal": BidderVrtcal, diff --git a/openrtb_ext/imp_valueimpression.go b/openrtb_ext/imp_valueimpression.go new file mode 100644 index 00000000000..7c5c70ee0a7 --- /dev/null +++ b/openrtb_ext/imp_valueimpression.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpValueImpression struct { + SiteId string `json:"siteId"` +} diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml new file mode 100644 index 00000000000..1d64abcb68f --- /dev/null +++ b/static/bidder-info/valueimpression.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "info@valueimpression.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/valueimpression.json b/static/bidder-params/valueimpression.json new file mode 100644 index 00000000000..5b9c32c592e --- /dev/null +++ b/static/bidder-params/valueimpression.json @@ -0,0 +1,15 @@ + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ValueImpression Adapter Params", + "description": "Schema to validate params accepted by the ValueImpression adapter", + + "type": "object", + "properties": { + "siteId": { + "type": "string", + "description": "Site ID" + } + }, + "required": ["siteId"] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index c58d552844d..c7ad70b7eff 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -50,6 +50,7 @@ import ( "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" "github.com/prebid/prebid-server/adapters/unruly" + "github.com/prebid/prebid-server/adapters/valueimpression" "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" @@ -111,6 +112,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index cc6d4b5870a..7aef9fa8b5a 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -59,6 +59,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderTripleliftNative): syncConfig, string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, + string(openrtb_ext.BidderValueImpression): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, From 95c269f3740c4c0e1bf82b481ca78f52e634cb17 Mon Sep 17 00:00:00 2001 From: rhaksi-kidoz <61601767+rhaksi-kidoz@users.noreply.github.com> Date: Tue, 17 Mar 2020 22:27:52 -0700 Subject: [PATCH 031/603] Kidoz adapter (#1210) Co-authored-by: Ryan Haksi --- .gitignore | 4 + adapters/kidoz/kidoz.go | 188 ++++++++++++++++++ adapters/kidoz/kidoz_test.go | 113 +++++++++++ .../kidoztest/exemplary/simple-banner.json | 71 +++++++ .../kidoztest/exemplary/simple-video.json | 69 +++++++ .../kidoz/kidoztest/supplemental/bad-bid.json | 96 +++++++++ .../supplemental/bidder-marshal.json | 30 +++ .../supplemental/empty-banner-format .json | 19 ++ .../kidoztest/supplemental/ext-marshal.json | 28 +++ .../supplemental/missing-banner-format.json | 18 ++ .../supplemental/missing-bidder.json | 25 +++ .../kidoztest/supplemental/missing-ext.json | 24 +++ .../supplemental/missing-kidoz-info.json | 52 +++++ .../supplemental/only-video-banner.json | 27 +++ .../kidoztest/supplemental/status-204.json | 57 ++++++ .../kidoztest/supplemental/status-400.json | 63 ++++++ .../kidoztest/supplemental/status-403.json | 63 ++++++ .../kidoztest/supplemental/status-408.json | 63 ++++++ .../kidoztest/supplemental/status-500.json | 63 ++++++ .../kidoztest/supplemental/status-502.json | 63 ++++++ .../kidoztest/supplemental/status-503.json | 58 ++++++ .../kidoztest/supplemental/status-504.json | 63 ++++++ adapters/kidoz/params_test.go | 79 ++++++++ analytics/config/testFiles/test-20200303 | 0 config/config.go | 1 + exchange/adapter_map.go | 5 +- go.mod | 1 + go.sum | 2 + openrtb_ext/bid.go | 6 +- openrtb_ext/bidders.go | 2 + openrtb_ext/imp_kidoz.go | 6 + static/bidder-info/kidoz.yaml | 11 + static/bidder-params/kidoz.json | 26 +++ usersync/usersyncers/syncer_test.go | 1 + validate.sh | 4 +- 35 files changed, 1394 insertions(+), 7 deletions(-) create mode 100644 adapters/kidoz/kidoz.go create mode 100644 adapters/kidoz/kidoz_test.go create mode 100644 adapters/kidoz/kidoztest/exemplary/simple-banner.json create mode 100644 adapters/kidoz/kidoztest/exemplary/simple-video.json create mode 100644 adapters/kidoz/kidoztest/supplemental/bad-bid.json create mode 100644 adapters/kidoz/kidoztest/supplemental/bidder-marshal.json create mode 100644 adapters/kidoz/kidoztest/supplemental/empty-banner-format .json create mode 100644 adapters/kidoz/kidoztest/supplemental/ext-marshal.json create mode 100644 adapters/kidoz/kidoztest/supplemental/missing-banner-format.json create mode 100644 adapters/kidoz/kidoztest/supplemental/missing-bidder.json create mode 100644 adapters/kidoz/kidoztest/supplemental/missing-ext.json create mode 100644 adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json create mode 100644 adapters/kidoz/kidoztest/supplemental/only-video-banner.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-204.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-400.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-403.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-408.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-500.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-502.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-503.json create mode 100644 adapters/kidoz/kidoztest/supplemental/status-504.json create mode 100644 adapters/kidoz/params_test.go create mode 100644 analytics/config/testFiles/test-20200303 create mode 100644 openrtb_ext/imp_kidoz.go create mode 100644 static/bidder-info/kidoz.yaml create mode 100644 static/bidder-params/kidoz.json diff --git a/.gitignore b/.gitignore index c2cbc1e97d5..60c24e79c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,10 @@ debug pbs.* inventory_url.yaml +# generated log files during tests +analytics/config/testFiles/ +analytics/filesystem/testFiles/ + # autogenerated version file # static/version.txt diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go new file mode 100644 index 00000000000..2d04cdffd39 --- /dev/null +++ b/adapters/kidoz/kidoz.go @@ -0,0 +1,188 @@ +package kidoz + +import ( + "encoding/json" + "errors" + "net/http" + "strconv" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type KidozAdapter struct { + endpoint string +} + +func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + impressions := request.Imp + result := make([]*adapters.RequestData, 0, len(impressions)) + errs := make([]error, 0, len(impressions)) + + for i, impression := range impressions { + if impression.Banner == nil && impression.Video == nil { + errs = append(errs, &errortypes.BadInput{ + Message: "Kidoz only supports banner or video ads", + }) + continue + } + + if impression.Banner != nil { + banner := impression.Banner + if banner.Format == nil { + errs = append(errs, &errortypes.BadInput{ + Message: "banner format required", + }) + continue + } + if len(banner.Format) == 0 { + errs = append(errs, &errortypes.BadInput{ + Message: "banner format array is empty", + }) + continue + } + } + + if len(impression.Ext) == 0 { + errs = append(errs, errors.New("impression extensions required")) + continue + } + var bidderExt adapters.ExtImpBidder + err := json.Unmarshal(impression.Ext, &bidderExt) + if err != nil { + errs = append(errs, err) + continue + } + if len(bidderExt.Bidder) == 0 { + errs = append(errs, errors.New("bidder required")) + continue + } + var impressionExt openrtb_ext.ExtImpKidoz + err = json.Unmarshal(bidderExt.Bidder, &impressionExt) + if err != nil { + errs = append(errs, err) + continue + } + if impressionExt.AccessToken == "" { + errs = append(errs, errors.New("Kidoz access_token required")) + continue + } + if impressionExt.PublisherID == "" { + errs = append(errs, errors.New("Kidoz publisher_id required")) + continue + } + + request.Imp = impressions[i : i+1] + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + result = append(result, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: body, + Headers: headers, + }) + } + + request.Imp = impressions + + if len(result) == 0 { + return nil, errs + } + return result, errs +} + +func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + switch responseData.StatusCode { + case http.StatusNoContent: + fallthrough + case http.StatusServiceUnavailable: + return nil, nil + + case http.StatusBadRequest: + fallthrough + case http.StatusUnauthorized: + fallthrough + case http.StatusForbidden: + return nil, []error{&errortypes.BadInput{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), + }} + + case http.StatusOK: + break + + default: + return nil, []error{&errortypes.BadServerResponse{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), + }} + } + + var bidResponse openrtb.BidResponse + err := json.Unmarshal(responseData.Body, &bidResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + thisBid := bid + bidType := GetMediaTypeForImp(bid.ImpID, request.Imp) + if bidType == UndefinedMediaType { + errs = append(errs, &errortypes.BadServerResponse{ + Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID, + }) + continue + } + response.Bids = append(response.Bids, &adapters.TypedBid{ + Bid: &thisBid, + BidType: bidType, + }) + } + } + + return response, errs +} + +func NewKidozBidder(endpoint string) *KidozAdapter { + return &KidozAdapter{ + endpoint: endpoint, + } +} + +const UndefinedMediaType = openrtb_ext.BidType("") + +func GetMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + var bidType openrtb_ext.BidType = UndefinedMediaType + for _, impression := range imps { + if impression.ID != impID { + continue + } + switch { + case impression.Banner != nil: + bidType = openrtb_ext.BidTypeBanner + case impression.Video != nil: + bidType = openrtb_ext.BidTypeVideo + case impression.Native != nil: + bidType = openrtb_ext.BidTypeNative + case impression.Audio != nil: + bidType = openrtb_ext.BidTypeAudio + } + break + } + return bidType +} diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go new file mode 100644 index 00000000000..55036c08614 --- /dev/null +++ b/adapters/kidoz/kidoz_test.go @@ -0,0 +1,113 @@ +package kidoz + +import ( + "math" + "net/http" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "kidoztest", NewKidozBidder("http://example.com/prebid")) +} + +func makeBidRequest() *openrtb.BidRequest { + request := &openrtb.BidRequest{ + ID: "test-req-id-0", + Imp: []openrtb.Imp{ + { + ID: "test-imp-id-0", + Banner: &openrtb.Banner{ + Format: []openrtb.Format{ + { + W: 320, + H: 50, + }, + }, + }, + Ext: []byte(`{"bidder":{"access_token":"token-0","publisher_id":"pub-0"}}`), + }, + }, + } + return request +} + +func TestMakeRequests(t *testing.T) { + kidoz := NewKidozBidder("http://example.com/prebid") + + t.Run("Handles Request marshal failure", func(t *testing.T) { + request := makeBidRequest() + request.Imp[0].BidFloor = math.Inf(1) // cant be marshalled + extra := &adapters.ExtraRequestInfo{} + reqs, errs := kidoz.MakeRequests(request, extra) + // cant assert message its different on different versions of go + assert.Equal(t, 1, len(errs)) + assert.Contains(t, errs[0].Error(), "json") + assert.Equal(t, 0, len(reqs)) + }) +} + +func TestMakeBids(t *testing.T) { + kidoz := NewKidozBidder("http://example.com/prebid") + + t.Run("Handles response marshal failure", func(t *testing.T) { + request := makeBidRequest() + requestData := &adapters.RequestData{} + responseData := &adapters.ResponseData{ + StatusCode: http.StatusOK, + } + + resp, errs := kidoz.MakeBids(request, requestData, responseData) + // cant assert message its different on different versions of go + assert.Equal(t, 1, len(errs)) + assert.Contains(t, errs[0].Error(), "JSON") + assert.Nil(t, resp) + }) +} + +func TestGetMediaTypeForImp(t *testing.T) { + imps := []openrtb.Imp{ + { + ID: "1", + Banner: &openrtb.Banner{}, + }, + { + ID: "2", + Video: &openrtb.Video{}, + }, + { + ID: "3", + Native: &openrtb.Native{}, + }, + { + ID: "4", + Audio: &openrtb.Audio{}, + }, + } + + t.Run("Bid not found is type empty string", func(t *testing.T) { + actual := GetMediaTypeForImp("ARGLE_BARGLE", imps) + assert.Equal(t, UndefinedMediaType, actual) + }) + t.Run("Can find banner type", func(t *testing.T) { + actual := GetMediaTypeForImp("1", imps) + assert.Equal(t, openrtb_ext.BidTypeBanner, actual) + }) + t.Run("Can find video type", func(t *testing.T) { + actual := GetMediaTypeForImp("2", imps) + assert.Equal(t, openrtb_ext.BidTypeVideo, actual) + }) + t.Run("Can find native type", func(t *testing.T) { + actual := GetMediaTypeForImp("3", imps) + assert.Equal(t, openrtb_ext.BidTypeNative, actual) + }) + t.Run("Can find audio type", func(t *testing.T) { + actual := GetMediaTypeForImp("4", imps) + assert.Equal(t, openrtb_ext.BidTypeAudio, actual) + }) +} diff --git a/adapters/kidoz/kidoztest/exemplary/simple-banner.json b/adapters/kidoz/kidoztest/exemplary/simple-banner.json new file mode 100644 index 00000000000..c44fdba7aeb --- /dev/null +++ b/adapters/kidoz/kidoztest/exemplary/simple-banner.json @@ -0,0 +1,71 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id-1", + "impid": "test-impression-id-1", + "price": 1 + } + ], + "seat": "kidoz" + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/exemplary/simple-video.json b/adapters/kidoz/kidoztest/exemplary/simple-video.json new file mode 100644 index 00000000000..3b682078cbe --- /dev/null +++ b/adapters/kidoz/kidoztest/exemplary/simple-video.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id-1", + "impid": "test-impression-id-1", + "price": 1 + } + ], + "seat": "kidoz" + } + ] + } + } + } + ] +} diff --git a/adapters/kidoz/kidoztest/supplemental/bad-bid.json b/adapters/kidoz/kidoztest/supplemental/bad-bid.json new file mode 100644 index 00000000000..32b8ec2cf06 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/bad-bid.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-0", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-0", + "publisher_id": "test-publisher-0" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-0", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-0", + "publisher_id": "test-publisher-0" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id-0", + "impid": "test-impression-id-0", + "price": 10 + }, + { + "id": "test-bid-id-bogus", + "impid": "test-impression-id-bogus", + "price": 11 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test-bid-id-0", + "impid": "test-impression-id-0", + "price": 10 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "ignoring bid id=test-bid-id-bogus, request doesn't contain any valid impression with id=test-impression-id-bogus", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json b/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json new file mode 100644 index 00000000000..8a8a5e76844 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-7", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": "invalid bidder" + } + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpKidoz", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json b/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json new file mode 100644 index 00000000000..18b62a4e1f4 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "banner format array is empty", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/ext-marshal.json b/adapters/kidoz/kidoztest/supplemental/ext-marshal.json new file mode 100644 index 00000000000..eaab459461a --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/ext-marshal.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-7", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": "invalid ext" + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json b/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json new file mode 100644 index 00000000000..3fdb5443c78 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "banner format required", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/missing-bidder.json b/adapters/kidoz/kidoztest/supplemental/missing-bidder.json new file mode 100644 index 00000000000..06ab222e322 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/missing-bidder.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": {} + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "bidder required", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/missing-ext.json b/adapters/kidoz/kidoztest/supplemental/missing-ext.json new file mode 100644 index 00000000000..8424a0b173a --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/missing-ext.json @@ -0,0 +1,24 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "impression extensions required", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json b/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json new file mode 100644 index 00000000000..bfe67aa7cea --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json @@ -0,0 +1,52 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-5", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-5" + } + } + }, + { + "id": "test-impression-id-6", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisher_id": "test-publisher-6" + } + } + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "Kidoz publisher_id required", + "comparison": "literal" + }, + { + "value": "Kidoz access_token required", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/only-video-banner.json b/adapters/kidoz/kidoztest/supplemental/only-video-banner.json new file mode 100644 index 00000000000..6e87e80806c --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/only-video-banner.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-0", + "audio": { + } + }, + { + "id": "test-impression-id-1", + "native": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Kidoz only supports banner or video ads", + "comparison": "literal" + }, + { + "value": "Kidoz only supports banner or video ads", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-204.json b/adapters/kidoz/kidoztest/supplemental/status-204.json new file mode 100644 index 00000000000..0bff102259a --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-204.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-400.json b/adapters/kidoz/kidoztest/supplemental/status-400.json new file mode 100644 index 00000000000..ca42aefdca0 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-400.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 400 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-403.json b/adapters/kidoz/kidoztest/supplemental/status-403.json new file mode 100644 index 00000000000..3b6d268ecfe --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-403.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 403, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 403 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-408.json b/adapters/kidoz/kidoztest/supplemental/status-408.json new file mode 100644 index 00000000000..8230967f3a8 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-408.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 408, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 408 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-500.json b/adapters/kidoz/kidoztest/supplemental/status-500.json new file mode 100644 index 00000000000..f734e6913a7 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-500.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 500 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-502.json b/adapters/kidoz/kidoztest/supplemental/status-502.json new file mode 100644 index 00000000000..b99f52a2e42 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-502.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 502, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 502 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-503.json b/adapters/kidoz/kidoztest/supplemental/status-503.json new file mode 100644 index 00000000000..f823372915c --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-503.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 503, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-504.json b/adapters/kidoz/kidoztest/supplemental/status-504.json new file mode 100644 index 00000000000..b996611eb97 --- /dev/null +++ b/adapters/kidoz/kidoztest/supplemental/status-504.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "access_token": "test-token-1", + "publisher_id": "test-publisher-1" + } + } + } + ] + } + }, + "mockResponse": { + "status": 504, + "body": "server text here" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 504 \"server text here\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kidoz/params_test.go b/adapters/kidoz/params_test.go new file mode 100644 index 00000000000..073d7382d68 --- /dev/null +++ b/adapters/kidoz/params_test.go @@ -0,0 +1,79 @@ +package kidoz + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderKidoz, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected kidoz params: %s \n Error: %s", validParam, err) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderKidoz, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisher_id":"pub-valid-0", "access_token":"token-valid-0"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"some_random_field":""}`, + `{"publisher_id":""}`, + `{"publisher_id": 1}`, + `{"publisher_id": 1.2}`, + `{"publisher_id": null}`, + `{"publisher_id": true}`, + `{"publisher_id": []}`, + `{"publisher_id": {}}`, + `{"publisher_id":"", "access_token":"token-valid-0"}`, + `{"publisher_id": 1, "access_token":"token-valid-0"}`, + `{"publisher_id": 1.2, "access_token":"token-valid-0"}`, + `{"publisher_id": null, "access_token":"token-valid-0"}`, + `{"publisher_id": true, "access_token":"token-valid-0"}`, + `{"publisher_id": [], "access_token":"token-valid-0"}`, + `{"publisher_id": {}, "access_token":"token-valid-0"}`, + `{"access_token":""}`, + `{"access_token": 1}`, + `{"access_token": 1.2}`, + `{"access_token": null}`, + `{"access_token": true}`, + `{"access_token": []}`, + `{"access_token": {}}`, + `{"access_token":"", "publisher_id":"pub-valid-0"}`, + `{"access_token": 1, "publisher_id":"pub-valid-0"}`, + `{"access_token": 1.2, "publisher_id":"pub-valid-0"}`, + `{"access_token": null, "publisher_id":"pub-valid-0"}`, + `{"access_token": true, "publisher_id":"pub-valid-0"}`, + `{"access_token": [], "publisher_id":"pub-valid-0"}`, + `{"access_token": {}, "publisher_id":"pub-valid-0"}`, + `{"access_token": 1, "publisher_id":"pub-valid-0"}`, + `{"access_token":"token-valid-0", "publisher_id": 1}`, +} diff --git a/analytics/config/testFiles/test-20200303 b/analytics/config/testFiles/test-20200303 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/config.go b/config/config.go index 2069730b692..d4edab2b53f 100644 --- a/config/config.go +++ b/config/config.go @@ -707,6 +707,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") + v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest") v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 7b841a2838e..05f44e24b66 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -5,8 +5,6 @@ import ( "net/http" "strings" - "github.com/prebid/prebid-server/adapters/kubient" - "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/adform" @@ -35,6 +33,8 @@ import ( "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/kidoz" + "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/marsmedia" @@ -101,6 +101,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), + openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), diff --git a/go.mod b/go.mod index af4bf5570a5..ea1f65efaa4 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 + github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index 06f07b1ece0..6d215da0af5 100644 --- a/go.sum +++ b/go.sum @@ -156,6 +156,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= +github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index c9c6f36332b..768128c96d6 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -42,9 +42,9 @@ type BidType string const ( BidTypeBanner BidType = "banner" - BidTypeVideo = "video" - BidTypeAudio = "audio" - BidTypeNative = "native" + BidTypeVideo BidType = "video" + BidTypeAudio BidType = "audio" + BidTypeNative BidType = "native" ) func BidTypes() []BidType { diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 627842f57ff..00c25f8a3f0 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -47,6 +47,7 @@ const ( BidderGumGum BidderName = "gumgum" BidderImprovedigital BidderName = "improvedigital" BidderIx BidderName = "ix" + BidderKidoz BidderName = "kidoz" BidderKubient BidderName = "kubient" BidderLifestreet BidderName = "lifestreet" BidderLockerDome BidderName = "lockerdome" @@ -107,6 +108,7 @@ var BidderMap = map[string]BidderName{ "gumgum": BidderGumGum, "improvedigital": BidderImprovedigital, "ix": BidderIx, + "kidoz": BidderKidoz, "kubient": BidderKubient, "lifestreet": BidderLifestreet, "lockerdome": BidderLockerDome, diff --git a/openrtb_ext/imp_kidoz.go b/openrtb_ext/imp_kidoz.go new file mode 100644 index 00000000000..45f9866a425 --- /dev/null +++ b/openrtb_ext/imp_kidoz.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpKidoz struct { + AccessToken string `json:"access_token"` + PublisherID string `json:"publisher_id"` +} diff --git a/static/bidder-info/kidoz.yaml b/static/bidder-info/kidoz.yaml new file mode 100644 index 00000000000..e2a9eee3fc7 --- /dev/null +++ b/static/bidder-info/kidoz.yaml @@ -0,0 +1,11 @@ +maintainer: + email: prebid-support@kidoz.net +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/kidoz.json b/static/bidder-params/kidoz.json new file mode 100644 index 00000000000..79e2edc2fd2 --- /dev/null +++ b/static/bidder-params/kidoz.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Kidoz Adapter Params", + "description": "A schema which validates params accepted by the Kidoz adapter", + "type": "object", + "properties": { + "access_token": { + "$ref": "#/definitions/non-empty-string", + "description": "Kidoz access_token" + }, + "publisher_id": { + "$ref": "#/definitions/non-empty-string", + "description": "Kidoz publisher_id" + } + }, + "definitions": { + "non-empty-string": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "access_token", + "publisher_id" + ] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7aef9fa8b5a..3de64ec1eb0 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -74,6 +74,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderTappx: true, openrtb_ext.BidderKubient: true, openrtb_ext.BidderPubnative: true, + openrtb_ext.BidderKidoz: true, } for bidder, config := range cfg.Adapters { diff --git a/validate.sh b/validate.sh index b5210550393..b81ade344d2 100755 --- a/validate.sh +++ b/validate.sh @@ -27,11 +27,11 @@ GOGLOB="${GOGLOB/ docs/}" GOGLOB="${GOGLOB/ vendor/}" # Check that there are no formatting issues -GOFMT_LINES=`gofmt -s -l $GOGLOB | wc -l | xargs` +GOFMT_LINES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | wc -l | xargs` if $AUTOFMT; then # if there are files with formatting issues, they will be automatically corrected using the gofmt -w command if [[ $GOFMT_LINES -ne 0 ]]; then - FMT_FILES=`gofmt -s -l $GOGLOB | xargs` + FMT_FILES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | xargs` for FILE in $FMT_FILES; do echo "Running: gofmt -s -w $FILE" `gofmt -s -w $FILE` From fb768950a1f625f267c1cdbce65f6668a62e38c8 Mon Sep 17 00:00:00 2001 From: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Date: Wed, 18 Mar 2020 15:14:43 +0000 Subject: [PATCH 032/603] Update auction.md (#1224) Fix type --- docs/endpoints/openrtb2/auction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index d670b092174..02183960791 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -336,7 +336,7 @@ Bids can be temporarily cached on the server by sending the following data as `r } ``` -Both `bids` and `vastxml` are optional, but one of the two is required. Thils property will have no effect +Both `bids` and `vastxml` are optional, but one of the two is required. This property will have no effect unless `request.ext.prebid.targeting` is also set in the request. If `bids` is present, Prebid Server will make a _best effort_ to include these extra From c3c87971d4dd1b151dec329f59fa00713c9ed95a Mon Sep 17 00:00:00 2001 From: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Date: Wed, 18 Mar 2020 15:15:16 +0000 Subject: [PATCH 033/603] Update auction.md (#1225) Fix typo. --- docs/endpoints/openrtb2/auction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index 02183960791..7795ef5afe0 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -174,7 +174,7 @@ will be truncated to only include the first 20 characters. #### Cookie syncs Each Bidder should receive their own ID in the `request.user.buyeruid` property. -Prebid Server has three ways to popualte this field. In order of priority: +Prebid Server has three ways to populate this field. In order of priority: 1. If the request payload contains `request.user.buyeruid`, then that value will be sent to all Bidders. In most cases, this is probably a bad idea. From dcc062a84d34f67b42fe34b64cca55e73ef926e4 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Wed, 18 Mar 2020 08:53:23 -0700 Subject: [PATCH 034/603] Added logging to cache for video endpoint (#1220) * WIP added logging to cache for video endpoint * Updating cache call to use TTL from config * Updates from initial feedback * Log now includes HTTP headers * Fixed caching to use a new cache entry rather than appending to the VAST * Added feature where is query is set, the test flag is set in the request * Updated recorded response and handleError * Updates from code review comments * Changed recorded output to be only the debug ext * Removed extra marhal calls * Changed cache to be an endpoint dependency * Added debugLog struct to hold all debug related info * Numerous smaller changes * Further code cleanup and added unit tests for debug changes * Added missing error checks * Added unit test for error case --- endpoints/openrtb2/amp_auction.go | 5 +- endpoints/openrtb2/amp_auction_test.go | 2 +- endpoints/openrtb2/auction.go | 7 +- endpoints/openrtb2/auction_test.go | 14 +- endpoints/openrtb2/video_auction.go | 83 +++++++-- endpoints/openrtb2/video_auction_test.go | 167 ++++++++++++++++++- exchange/auction.go | 14 +- exchange/auction_test.go | 3 +- exchange/cachetest/debuglog_disabled.json | 54 ++++++ exchange/cachetest/debuglog_enabled.json | 58 +++++++ exchange/exchange.go | 31 +++- exchange/exchange_test.go | 28 +++- exchange/exchangetest/debuglog_disabled.json | 161 ++++++++++++++++++ exchange/exchangetest/debuglog_enabled.json | 161 ++++++++++++++++++ exchange/targeting_test.go | 2 +- router/router.go | 2 +- 16 files changed, 749 insertions(+), 43 deletions(-) create mode 100644 exchange/cachetest/debuglog_disabled.json create mode 100644 exchange/cachetest/debuglog_enabled.json create mode 100644 exchange/exchangetest/debuglog_disabled.json create mode 100644 exchange/exchangetest/debuglog_enabled.json diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index d92f9d0ae61..8edc1e13787 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -71,7 +71,8 @@ func NewAmpEndpoint( disabledBidders, defRequest, defReqJSON, - bidderMap}).AmpAuction), nil + bidderMap, + nil}).AmpAuction), nil } @@ -165,7 +166,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h return } - response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories) + response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil) ao.AuctionResponse = response if err != nil { diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index c62a6a710d5..39d1e13c50d 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -902,7 +902,7 @@ type mockAmpExchange struct { lastRequest *openrtb.BidRequest } -func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest response := &openrtb.BidResponse{ diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index a0ed19e5fa4..d9c31eca98c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -27,6 +27,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/prebid" + "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" @@ -55,7 +56,8 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato disabledBidders, defRequest, defReqJSON, - bidderMap}).Auction), nil + bidderMap, + nil}).Auction), nil } type endpointDeps struct { @@ -71,6 +73,7 @@ type endpointDeps struct { defaultRequest bool defReqJSON []byte bidderMap map[string]openrtb_ext.BidderName + cache prebid_cache_client.Client } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -137,7 +140,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http return } - response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories) + response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil) ao.Request = req ao.Response = response if err != nil { diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 89f0fa255df..74a70c69415 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -602,7 +602,7 @@ func TestStoredRequests(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap} + edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap, nil} for i, requestData := range testStoredRequests { newRequest, errList := edep.processStoredRequests(context.Background(), json.RawMessage(requestData)) @@ -638,6 +638,7 @@ func TestOversizedRequest(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -670,6 +671,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -807,6 +809,7 @@ func TestDisabledBidder(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -840,6 +843,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } errs := deps.validateImpExt(imp, nil, 0) assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext)) @@ -878,6 +882,7 @@ func TestCurrencyTrunc(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } ui := uint64(1) @@ -919,6 +924,7 @@ func TestCCPAInvalidValueWarning(t *testing.T) { false, []byte{}, openrtb_ext.BidderMap, + nil, } ui := uint64(1) @@ -953,7 +959,7 @@ type nobidExchange struct { gotRequest *openrtb.BidRequest } -func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { e.gotRequest = bidRequest return &openrtb.BidResponse{ ID: bidRequest.ID, @@ -964,7 +970,7 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.Bid type brokenExchange struct{} -func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { return nil, errors.New("Critical, unrecoverable error.") } @@ -1324,7 +1330,7 @@ type mockExchange struct { lastRequest *openrtb.BidRequest } -func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest return &openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{{ diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 2a8663959a6..630a3f5acd3 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -14,6 +14,7 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" + "github.com/gofrs/uuid" "github.com/prebid/prebid-server/errortypes" "github.com/golang/glog" @@ -24,20 +25,21 @@ import ( "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" + "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/usersync" ) var defaultRequestTimeout int64 = 5000 -func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) { +func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) { if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { return nil, errors.New("NewVideoEndpoint requires non-nil arguments.") } defRequest := defReqJSON != nil && len(defReqJSON) > 0 - return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap}).VideoAuctionEndpoint), nil + return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap, cache}).VideoAuctionEndpoint), nil } /* @@ -79,7 +81,38 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re CookieFlag: pbsmetrics.CookieFlagUnknown, RequestStatus: pbsmetrics.RequestStatusOK, } + + debugQuery := r.URL.Query().Get("debug") + cacheTTL := int64(3600) + if deps.cfg.CacheURL.DefaultTTLs.Video > 0 { + cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video) + } + debugLog := exchange.DebugLog{ + EnableDebug: strings.EqualFold(debugQuery, "true"), + CacheType: prebid_cache_client.TypeXML, + TTL: cacheTTL, + } + defer func() { + if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { + debugLog.Data = fmt.Sprintf("", debugLog.Data) + data, err := json.Marshal(debugLog.Data) + if err == nil { + toCache := []prebid_cache_client.Cacheable{ + { + Type: debugLog.CacheType, + Data: data, + TTLSeconds: debugLog.TTL, + Key: "log_" + debugLog.CacheKey, + }, + } + if deps.cache != nil { + ctx, cancel := context.WithDeadline(context.Background(), start.Add(time.Duration(100)*time.Millisecond)) + defer cancel() + deps.cache.PutJson(ctx, toCache) + } + } + } deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) deps.analytics.LogVideoObject(&vo) @@ -91,38 +124,46 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } requestJson, err := ioutil.ReadAll(lr) if err != nil { - handleError(&labels, w, []error{err}, &vo) + handleError(&labels, w, []error{err}, &vo, &debugLog) return } resolvedRequest := requestJson + if debugLog.EnableDebug { + debugLog.Data = fmt.Sprintf("Request:\n%s", string(requestJson)) + if headerBytes, err := json.Marshal(r.Header); err == nil { + debugLog.Data = fmt.Sprintf("%s\n\nHeaders:\n%s", debugLog.Data, string(headerBytes)) + } else { + debugLog.Data = fmt.Sprintf("%s\n\nUnable to marshal headers data\n", debugLog.Data) + } + } //load additional data - stored simplified req storedRequestId, err := getVideoStoredRequestId(requestJson) if err != nil { if deps.cfg.VideoStoredRequestRequired { - handleError(&labels, w, []error{err}, &vo) + handleError(&labels, w, []error{err}, &vo, &debugLog) return } } else { storedRequest, errs := deps.loadStoredVideoRequest(context.Background(), storedRequestId) if len(errs) > 0 { - handleError(&labels, w, errs, &vo) + handleError(&labels, w, errs, &vo, &debugLog) return } //merge incoming req with stored video req resolvedRequest, err = jsonpatch.MergePatch(storedRequest, requestJson) if err != nil { - handleError(&labels, w, []error{err}, &vo) + handleError(&labels, w, []error{err}, &vo, &debugLog) return } } //unmarshal and validate combined result videoBidReq, errL, podErrors := deps.parseVideoRequest(resolvedRequest, r.Header) if len(errL) > 0 { - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } @@ -132,13 +173,17 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re if deps.defaultRequest { if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil { err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err) - handleError(&labels, w, []error{err}, &vo) + handleError(&labels, w, []error{err}, &vo, &debugLog) return } } //create full open rtb req from full video request mergeData(videoBidReq, bidReq) + // If debug query param is set, force the response to enable test flag + if debugLog.EnableDebug { + bidReq.Test = 1 + } initialPodNumber := len(videoBidReq.PodConfig.Pods) if len(podErrors) > 0 { @@ -156,7 +201,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } err := errors.New(fmt.Sprintf("all pods are incorrect: %s", strings.Join(resPodErr, "; "))) errL = append(errL, err) - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } @@ -168,7 +213,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re errL = deps.validateRequest(bidReq) if len(errL) > 0 { - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } @@ -196,16 +241,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { errL = append(errL, acctIdErr) - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } //execute auction logic - response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories) + response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories, &debugLog) vo.Request = bidReq vo.Response = response if err != nil { errL := []error{err} - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } @@ -213,7 +258,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidResp, err := buildVideoResponse(response, podErrors) if err != nil { errL := []error{err} - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } if bidReq.Test == 1 { @@ -226,7 +271,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re //resp, err := json.Marshal(response) if err != nil { errL := []error{err} - handleError(&labels, w, errL, &vo) + handleError(&labels, w, errL, &vo, &debugLog) return } @@ -242,7 +287,13 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P return videoReq } -func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject) { +func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) { + if debugLog != nil && debugLog.EnableDebug { + if rawUUID, err := uuid.NewV4(); err == nil { + debugLog.CacheKey = rawUUID.String() + } + errL = append(errL, fmt.Errorf("[Debug cache ID: %s]", debugLog.CacheKey)) + } labels.RequestStatus = pbsmetrics.RequestStatusErr var errors string var status int = http.StatusInternalServerError diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index a5ad62c9fa8..0199b43f610 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -17,6 +17,7 @@ import ( "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" + "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" metrics "github.com/rcrowley/go-metrics" @@ -171,6 +172,112 @@ func TestCreateBidExtensionExactDurTrueNoPriceRange(t *testing.T) { assert.Equal(t, resExt.Prebid.Targeting.PriceGranularity, openrtb_ext.PriceGranularityFromString("med"), "Price granularity is incorrect") } +func TestVideoEndpointDebugQueryTrue(t *testing.T) { + ex := &mockExchangeVideo{ + cache: &mockCacheClient{}, + } + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + reqBody := string(getRequestPayload(t, reqData)) + req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps := mockDeps(t, ex) + deps.VideoAuctionEndpoint(recorder, req, nil) + + if ex.lastRequest == nil { + t.Fatalf("The request never made it into the Exchange.") + } + if !ex.cache.called { + t.Fatalf("Cache was not called when it should have been") + } + + respBytes := recorder.Body.Bytes() + resp := &openrtb_ext.BidResponseVideo{} + if err := json.Unmarshal(respBytes, resp); err != nil { + t.Fatalf("Unable to umarshal response.") + } + + assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") + assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") + assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + + assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") + assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") + + assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response") +} + +func TestVideoEndpointDebugQueryFalse(t *testing.T) { + ex := &mockExchangeVideo{ + cache: &mockCacheClient{}, + } + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + reqBody := string(getRequestPayload(t, reqData)) + req := httptest.NewRequest("POST", "/openrtb2/video?debug=123", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps := mockDeps(t, ex) + deps.VideoAuctionEndpoint(recorder, req, nil) + + if ex.lastRequest == nil { + t.Fatalf("The request never made it into the Exchange.") + } + if ex.cache.called { + t.Fatalf("Cache was called when it shouldn't have been") + } + + respBytes := recorder.Body.Bytes() + resp := &openrtb_ext.BidResponseVideo{} + if err := json.Unmarshal(respBytes, resp); err != nil { + t.Fatalf("Unable to umarshal response.") + } + + assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") + assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") + assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + + assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") + assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") + assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") + + assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response") +} + +func TestVideoEndpointDebugError(t *testing.T) { + ex := &mockExchangeVideo{ + cache: &mockCacheClient{}, + } + reqData, err := ioutil.ReadFile("sample-requests/video/video_invalid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + reqBody := string(getRequestPayload(t, reqData)) + req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps := mockDeps(t, ex) + deps.VideoAuctionEndpoint(recorder, req, nil) + + if !ex.cache.called { + t.Fatalf("Cache was not called when it should have been") + } + + assert.Equal(t, recorder.Code, 500, "Should catch error in request") +} + func TestVideoEndpointNoPods(t *testing.T) { ex := &mockExchangeVideo{} reqData, err := ioutil.ReadFile("sample-requests/video/video_invalid_sample.json") @@ -714,7 +821,7 @@ func TestHandleError(t *testing.T) { recorder := httptest.NewRecorder() err1 := errors.New("Error for testing handleError 1") err2 := errors.New("Error for testing handleError 2") - handleError(&labels, recorder, []error{err1, err2}, &vo) + handleError(&labels, recorder, []error{err1, err2}, &vo, nil) assert.Equal(t, pbsmetrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error") assert.Equal(t, 500, recorder.Code, "Error status should be written to writer") @@ -820,6 +927,41 @@ func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) { } +func TestHandleErrorDebugLog(t *testing.T) { + vo := analytics.VideoObject{ + Status: 200, + Errors: make([]error, 0), + } + + labels := pbsmetrics.Labels{ + Source: pbsmetrics.DemandUnknown, + RType: pbsmetrics.ReqTypeVideo, + PubID: pbsmetrics.PublisherUnknown, + Browser: "test browser", + CookieFlag: pbsmetrics.CookieFlagUnknown, + RequestStatus: pbsmetrics.RequestStatusOK, + } + + recorder := httptest.NewRecorder() + err1 := errors.New("Error for testing handleError 1") + err2 := errors.New("Error for testing handleError 2") + debugLog := exchange.DebugLog{ + EnableDebug: true, + CacheType: prebid_cache_client.TypeXML, + Data: "test debug data", + TTL: int64(3600), + } + handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog) + + assert.Equal(t, pbsmetrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error") + assert.Equal(t, 500, recorder.Code, "Error status should be written to writer") + assert.Equal(t, 500, vo.Status, "Analytics object should have error status") + assert.Equal(t, 3, len(vo.Errors), "New errors including debug cache ID should be appended to Analytics object Errors") + assert.Equal(t, "Error for testing handleError 1", vo.Errors[0].Error(), "Error in Analytics object should have test error message for first error") + assert.Equal(t, "Error for testing handleError 2", vo.Errors[1].Error(), "Error in Analytics object should have test error message for second error") + assert.NotEmpty(t, debugLog.CacheKey, "DebugLog CacheKey value should have been set") +} + func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} @@ -836,6 +978,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p false, []byte{}, openrtb_ext.BidderMap, + nil, } return edep, theMetrics, mockModule @@ -875,11 +1018,27 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { false, []byte{}, openrtb_ext.BidderMap, + ex.cache, } return edep } +type mockCacheClient struct { + called bool +} + +func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) { + if !m.called { + m.called = true + } + return []string{}, []error{} +} + +func (m *mockCacheClient) GetExtCacheData() (string, string) { + return "", "" +} + type mockVideoStoredReqFetcher struct { } @@ -889,10 +1048,14 @@ func (cf mockVideoStoredReqFetcher) FetchRequests(ctx context.Context, requestID type mockExchangeVideo struct { lastRequest *openrtb.BidRequest + cache *mockCacheClient } -func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest + if debugLog != nil && debugLog.EnableDebug { + m.cache.called = true + } ext := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"20.00","hb_pb_cat_dur":"20.00_395_30s","hb_size":"1x1", "hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) return &openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{{ diff --git a/exchange/auction.go b/exchange/auction.go index 2b9a8cb58fc..9909b78dd87 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -60,7 +60,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity a.roundedPrices = roundedPrices } -func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string) []error { +func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error { var bids, vast, includeBidderKeys, includeWinners bool = targData.includeCacheBids, targData.includeCacheVast, targData.includeBidderKeys, targData.includeWinners if !((bids || vast) && (includeBidderKeys || includeWinners)) { return nil @@ -147,6 +147,18 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } + if debugLog != nil && debugLog.EnableDebug { + debugLog.CacheKey = hbCacheID + if jsonBytes, err := json.Marshal(debugLog.Data); err == nil { + toCache = append(toCache, prebid_cache_client.Cacheable{ + Type: debugLog.CacheType, + Data: jsonBytes, + TTLSeconds: debugLog.TTL, + Key: "log_" + debugLog.CacheKey, + }) + } + } + ids, err := cache.PutJson(ctx, toCache) if err != nil { errs = append(errs, err...) diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ea19732d82b..d23ff03e00a 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -188,7 +188,7 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { winningBidsByBidder: winningBidsByBidder, roundedPrices: roundedPrices, } - _ = testAuction.doCache(ctx, cache, targData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory) + _ = testAuction.doCache(ctx, cache, targData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory, &specData.DebugLog) if len(specData.ExpectedCacheables) > len(cache.items) { t.Errorf("%s: [CACHE_ERROR] Less elements were cached than expected \n", fileDisplayName) @@ -232,6 +232,7 @@ type cacheSpec struct { TargetDataIncludeBidderKeys bool `json:"targetDataIncludeBidderKeys"` TargetDataIncludeCacheBids bool `json:"targetDataIncludeCacheBids"` TargetDataIncludeCacheVast bool `json:"targetDataIncludeCacheVast"` + DebugLog DebugLog `json:"debugLog,omitempty"` } type pbsBid struct { diff --git a/exchange/cachetest/debuglog_disabled.json b/exchange/cachetest/debuglog_disabled.json new file mode 100644 index 00000000000..675488c04d1 --- /dev/null +++ b/exchange/cachetest/debuglog_disabled.json @@ -0,0 +1,54 @@ +{ + "debugLog": { + "EnableDebug": false, + "CacheType": "xml", + "TTL": 3600, + "Data": "test debug data" + }, + "bidRequest": { + "imp": [{ + "id": "oneImp", + "exp": 600 + }, { + "id": "twoImp" + }] + }, + "pbsBids": [{ + "bid":{ + "id": "bidOne", + "impid": "oneImp", + "price": 7.64 + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "bidTwo", + "impid": "twoImp", + "price": 5.64 + }, + "bidType": "video", + "bidder": "pubmatic" + }], + "expectedCacheables": [ + { + "Type": "json", + "TTLSeconds": 660, + "Data": "{\"id\": \"bidOne\", \"impid\": \"oneImp\", \"price\": 7.64}" + }, { + "Type": "json", + "TTLSeconds": 3660, + "Data": "{\"id\": \"bidTwo\", \"impid\": \"twoImp\", \"price\": 5.64}" + } + ], + "defaultTTLs": { + "banner": 300, + "video": 3600, + "audio": 1800, + "native": 300 + }, + "targetDataIncludeWinners":true, + "targetDataIncludeBidderKeys":true, + "targetDataIncludeCacheBids":true, + "targetDataIncludeCacheVast":false +} diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json new file mode 100644 index 00000000000..d4486558a54 --- /dev/null +++ b/exchange/cachetest/debuglog_enabled.json @@ -0,0 +1,58 @@ +{ + "debugLog": { + "EnableDebug": true, + "CacheType": "xml", + "TTL": 3600, + "Data": "test debug data" + }, + "bidRequest": { + "imp": [{ + "id": "oneImp", + "exp": 600 + }, { + "id": "twoImp" + }] + }, + "pbsBids": [{ + "bid":{ + "id": "bidOne", + "impid": "oneImp", + "price": 7.64 + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "bidTwo", + "impid": "twoImp", + "price": 5.64 + }, + "bidType": "video", + "bidder": "pubmatic" + }], + "expectedCacheables": [ + { + "Type": "json", + "TTLSeconds": 660, + "Data": "{\"id\": \"bidOne\", \"impid\": \"oneImp\", \"price\": 7.64}" + }, { + "Type": "json", + "TTLSeconds": 3660, + "Data": "{\"id\": \"bidTwo\", \"impid\": \"twoImp\", \"price\": 5.64}" + }, { + "Type": "xml", + "TTLSeconds": 3600, + "Data": "test debug data" + } + ], + "defaultTTLs": { + "banner": 300, + "video": 3600, + "audio": 1800, + "native": 300 + }, + "targetDataIncludeWinners":true, + "targetDataIncludeBidderKeys":true, + "targetDataIncludeCacheBids":true, + "targetDataIncludeCacheVast":false +} diff --git a/exchange/exchange.go b/exchange/exchange.go index ef10180a745..995add3d496 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -27,10 +27,18 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" ) +type DebugLog struct { + EnableDebug bool + CacheType prebid_cache_client.PayloadType + Data string + TTL int64 + CacheKey string +} + // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines. type Exchange interface { // HoldAuction executes an OpenRTB v2.5 Auction. - HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) + HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) } // IdFetcher can find the user's ID for a specific Bidder. @@ -78,7 +86,7 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con return e } -func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) { +func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) { // Snapshot of resolved bid request for debug if test request resolvedRequest, err := buildResolvedRequest(bidRequest) if err != nil { @@ -142,6 +150,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, cleanRequests, aliases, bidAdjustmentFactors, blabels, conversions) var auc *auction = nil + var bidResponseExt *openrtb_ext.ExtBidResponse = nil if anyBidsReturned { var bidCategory map[string]string @@ -162,7 +171,15 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque if targData != nil { auc.setRoundedPrices(targData.priceGranularity) - cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory) + if debugLog != nil && debugLog.EnableDebug { + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errs) + if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + debugLog.Data = fmt.Sprintf("", debugLog.Data, string(bidRespExtBytes)) + } else { + errs = append(errs, errors.New("Unable to marshal response ext for debugging")) + } + } + cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog) if len(cacheErrs) > 0 { errs = append(errs, cacheErrs...) } @@ -176,7 +193,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } // Build the response - return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, errs) + return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, bidResponseExt, errs) } type DealTierInfo struct { @@ -394,7 +411,7 @@ func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, resolvedRequest json.RawMessage, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, errList []error) (*openrtb.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, resolvedRequest json.RawMessage, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, errList []error) (*openrtb.BidResponse, error) { bidResponse := new(openrtb.BidResponse) bidResponse.ID = bidRequest.ID @@ -417,7 +434,9 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ bidResponse.SeatBid = seatBids - bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errList) + if bidResponseExt == nil { + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errList) + } buffer := &bytes.Buffer{} enc := json.NewEncoder(buffer) enc.SetEscapeHTML(false) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 0a64bce0826..7217e609189 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -127,7 +127,7 @@ func TestCharacterEscape(t *testing.T) { var errList []error /* 4) Build bid response */ - bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, errList) + bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList) /* 5) Assert we have no errors and one '&' character as we are supposed to */ if err != nil { @@ -279,7 +279,7 @@ func TestGetBidCacheInfo(t *testing.T) { var errList []error /* 4) Build bid response */ - bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, errList) + bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, nil, errList) /* 5) Assert we have no errors and the bid response we expected*/ assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") @@ -450,7 +450,7 @@ func TestBidResponseCurrency(t *testing.T) { // Run tests for i := range testCases { - actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, errList) + actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList) assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err)) assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext)) } @@ -489,7 +489,7 @@ func TestRaceIntegration(t *testing.T) { } theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()) - _, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher) + _, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -666,7 +666,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - _, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher) + _, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -732,7 +732,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher) + debugLog := &DebugLog{} + if spec.DebugLog != nil { + *debugLog = *spec.DebugLog + } + bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher, debugLog) responseTimes := extractResponseTimes(t, filename, bid) for _, bidderName := range biddersInAuction { if _, ok := responseTimes[bidderName]; !ok { @@ -751,6 +755,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } } } + if spec.DebugLog != nil { + if spec.DebugLog.EnableDebug { + if len(debugLog.Data) <= len(spec.DebugLog.Data) { + t.Errorf("%s: DebugLog was not modified when it should have been", filename) + } + } else { + if !strings.EqualFold(spec.DebugLog.Data, debugLog.Data) { + t.Errorf("%s: DebugLog was modified when it shouldn't have been", filename) + } + } + } } func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) []string { @@ -1588,6 +1603,7 @@ type exchangeSpec struct { OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` Response exchangeResponse `json:"response,omitempty"` EnforceCCPA bool `json:"enforceCcpa"` + DebugLog *DebugLog `json:"debuglog,omitempty"` } type exchangeRequest struct { diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json new file mode 100644 index 00000000000..0c24c121935 --- /dev/null +++ b/exchange/exchangetest/debuglog_disabled.json @@ -0,0 +1,161 @@ +{ + "debugLog": { + "EnableDebug": false, + "CacheType": "xml", + "TTL": 3600, + "Data": "test debug data" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher":"", + "withcategory": true + } + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": ["IAB1-1"] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + }, + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": ["IAB1-2"] + }, + "bidType": "video" + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": ["IAB1-1"], + "ext": { + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + }, + { + "cat": ["IAB1-2"], + "crid": "creative-3", + "ext": { + "prebid": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.50", + "hb_pb_appnexus": "0.50", + "hb_pb_cat_dur": "0.50_HomeDecor_0s", + "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s", + "hb_size": "300x500", + "hb_size_appnexus": "300x500" + }, + "type": "video" + } + }, + "h": 500, + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300 + } + ] + } + ] + } + } + } + \ No newline at end of file diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json new file mode 100644 index 00000000000..281bf3a1b4e --- /dev/null +++ b/exchange/exchangetest/debuglog_enabled.json @@ -0,0 +1,161 @@ +{ + "debugLog": { + "EnableDebug": true, + "CacheType": "xml", + "TTL": 3600, + "Data": "test debug data" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher":"", + "withcategory": true + } + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": ["IAB1-1"] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + }, + { + "ortbBid": { + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300, + "h": 500, + "crid": "creative-3", + "cat": ["IAB1-2"] + }, + "bidType": "video" + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": ["IAB1-1"], + "ext": { + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + }, + { + "cat": ["IAB1-2"], + "crid": "creative-3", + "ext": { + "prebid": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.50", + "hb_pb_appnexus": "0.50", + "hb_pb_cat_dur": "0.50_HomeDecor_0s", + "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s", + "hb_size": "300x500", + "hb_size_appnexus": "300x500" + }, + "type": "video" + } + }, + "h": 500, + "id": "apn-other-bid", + "impid": "imp-id-2", + "price": 0.6, + "w": 300 + } + ] + } + ] + } + } + } + \ No newline at end of file diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 92b338f97fb..f86309684c6 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -106,7 +106,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher) + bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) if err != nil { t.Fatalf("Unexpected errors running auction: %v", err) diff --git a/router/router.go b/router/router.go index 7e713ca637a..8ac463b85a0 100644 --- a/router/router.go +++ b/router/router.go @@ -251,7 +251,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) + videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } From c7ead0710a10624a767d28a14ee3f1e3b7278ec7 Mon Sep 17 00:00:00 2001 From: Aadesh Date: Wed, 25 Mar 2020 14:58:02 -0400 Subject: [PATCH 035/603] added VISX vendor ID for usersyncing (#1229) Co-authored-by: Aadesh Patel --- adapters/visx/usersync.go | 2 +- adapters/visx/usersync_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/visx/usersync.go b/adapters/visx/usersync.go index 0ceb58c505f..fe1f5a42a10 100644 --- a/adapters/visx/usersync.go +++ b/adapters/visx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewVisxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("visx", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("visx", 154, temp, adapters.SyncTypeRedirect) } diff --git a/adapters/visx/usersync_test.go b/adapters/visx/usersync_test.go index 01e80e644c5..a77136c9240 100644 --- a/adapters/visx/usersync_test.go +++ b/adapters/visx/usersync_test.go @@ -30,6 +30,6 @@ func TestVisxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://t.visx.net/s2s_sync?gdpr=A&gdpr_consent=B&us_privacy=C&redir=%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.EqualValues(t, 154, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } From 145c5259042a09903a29f7b998d631f1782b92c7 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 25 Mar 2020 16:59:19 -0400 Subject: [PATCH 036/603] First pass at phase 1 TCF 2.0 support (#1228) * First pass at phase 1 TCF 2.0 support * minor fixes * Update go-gdpr library and fix stuff * Fixes for PR comments --- gdpr/gdpr.go | 14 +++++-- gdpr/impl.go | 28 ++++++++++---- gdpr/impl_test.go | 63 +++++++++++++++++++++++--------- gdpr/vendorlist-fetching.go | 46 ++++++++++++++--------- gdpr/vendorlist-fetching_test.go | 24 ++++++------ go.mod | 2 +- go.sum | 4 +- 7 files changed, 122 insertions(+), 59 deletions(-) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index bdba008a77a..a6b64203a95 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -25,6 +26,11 @@ type Permissions interface { PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) } +const ( + tCF1 uint8 = 1 + tCF2 uint8 = 2 +) + // NewPermissions gets an instance of the Permissions for use elsewhere in the project. func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ext.BidderName]uint16, client *http.Client) Permissions { // If the host doesn't buy into the IAB GDPR consent framework, then save some cycles and let all syncs happen. @@ -33,9 +39,11 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ } return &permissionsImpl{ - cfg: cfg, - vendorIDs: vendorIDs, - fetchVendorList: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker), + cfg: cfg, + vendorIDs: vendorIDs, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tCF1), + tCF2: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tCF2)}, } } diff --git a/gdpr/impl.go b/gdpr/impl.go index 2fe6a67e99f..615c3a090c9 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -3,7 +3,9 @@ package gdpr import ( "context" - "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/go-gdpr/api" + tcf1constants "github.com/prebid/go-gdpr/consentconstants" + consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" @@ -18,7 +20,7 @@ import ( type permissionsImpl struct { cfg config.GDPR vendorIDs map[openrtb_ext.BidderName]uint16 - fetchVendorList func(ctx context.Context, id uint16) (vendorlist.VendorList, error) + fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { @@ -71,10 +73,10 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } + // InfoStorageAccess is the same across TCF 1 and TCF 2 if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { return true, nil } - return false, nil } @@ -93,14 +95,20 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent return false, nil } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(consentconstants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(consentconstants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, nil + if parsedConsent.Version() == 2 { + // Need to add the location special purpose once the library supports it. + if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) { + return true, nil + } + } else { + if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { + return true, nil + } } - return false, nil } -func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent vendorconsent.VendorConsents, vendor vendorlist.Vendor, err error) { +func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) { parsedConsent, err = vendorconsent.ParseString(consent) if err != nil { err = &ErrorMalformedConsent{ @@ -110,7 +118,11 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons return } - vendorList, err := p.fetchVendorList(ctx, parsedConsent.VendorListVersion()) + version := parsedConsent.Version() + if version < 1 || version > 2 { + return + } + vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) if err != nil { return } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index a1d4af3346d..8b89577d6c8 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -18,8 +18,11 @@ func TestNoConsentButAllowByDefault(t *testing.T) { HostVendorID: 3, UsersyncIfAmbiguous: true, }, - vendorIDs: nil, - fetchVendorList: failedListFetcher, + vendorIDs: nil, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: failedListFetcher, + tCF2: failedListFetcher, + }, } allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") assertBoolsEqual(t, true, allowSync) @@ -35,8 +38,11 @@ func TestNoConsentAndRejectByDefault(t *testing.T) { HostVendorID: 3, UsersyncIfAmbiguous: false, }, - vendorIDs: nil, - fetchVendorList: failedListFetcher, + vendorIDs: nil, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: failedListFetcher, + tCF2: failedListFetcher, + }, } allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") assertBoolsEqual(t, false, allowSync) @@ -63,9 +69,14 @@ func TestAllowedSyncs(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 3, }, - fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + }, } allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABoAAAAAMw") @@ -94,9 +105,14 @@ func TestProhibitedPurposes(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 3, }, - fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + }, } allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABAAAAAAMw") @@ -125,9 +141,14 @@ func TestProhibitedVendors(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 3, }, - fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + }, } allowSync, err := perms.HostCookiesAllowed(context.Background(), "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") @@ -144,7 +165,10 @@ func TestMalformedConsent(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, }, - fetchVendorList: listFetcher(nil), + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: listFetcher(nil), + tCF2: listFetcher(nil), + }, } sync, err := perms.HostCookiesAllowed(context.Background(), "BON") @@ -169,9 +193,14 @@ func TestAllowPersonalInfo(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 3, }, - fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 1: parseVendorListData(t, vendorListData), + }), + }, } // PI needs both purposes to succeed diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index d492e9e5e11..987622a6a8a 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -11,12 +11,14 @@ import ( "time" "github.com/golang/glog" + "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/go-gdpr/vendorlist2" "github.com/prebid/prebid-server/config" "golang.org/x/net/context/ctxhttp" ) -type saveVendors func(uint16, vendorlist.VendorList) +type saveVendors func(uint16, api.VendorList) // This file provides the vendorlist-fetching function for Prebid Server. // @@ -24,22 +26,22 @@ type saveVendors func(uint16, vendorlist.VendorList) // // Nothing in this file is exported. Public APIs can be found in gdpr.go -func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { +func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { // These save and load functions can be used to store & retrieve lists from our cache. save, load := newVendorListCache() withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) defer cancel() - populateCache(withTimeout, client, urlMaker, save) + populateCache(withTimeout, client, urlMaker, save, TCFVer) - saveOneSometimes := newOccasionalSaver(cfg.Timeouts.ActiveTimeout()) + saveOneSometimes := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), TCFVer) return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { list := load(id) if list != nil { return list, nil } - saveOneSometimes(ctx, client, urlMaker(id), save) + saveOneSometimes(ctx, client, urlMaker(id, TCFVer), save) list = load(id) if list != nil { return list, nil @@ -49,17 +51,23 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http } // populateCache saves all the known versions of the vendor list for future use. -func populateCache(ctx context.Context, client *http.Client, urlMaker func(uint16) string, saver saveVendors) { - latestVersion := saveOne(ctx, client, urlMaker(0), saver) +func populateCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, TCFVer uint8) { + latestVersion := saveOne(ctx, client, urlMaker(0, TCFVer), saver, TCFVer) for i := uint16(1); i < latestVersion; i++ { - saveOne(ctx, client, urlMaker(i), saver) + saveOne(ctx, client, urlMaker(i, TCFVer), saver, TCFVer) } } // Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0, // this will fetch the latest version. -func vendorListURLMaker(version uint16) string { +func vendorListURLMaker(version uint16, TCFVer uint8) string { + if TCFVer == 2 { + if version == 0 { + return "https://vendorlist.consensu.org/v2/vendor-list.json" + } + return "https://vendorlist.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(version)) + ".json" + } if version == 0 { return "https://vendorlist.consensu.org/vendorlist.json" } @@ -71,7 +79,7 @@ func vendorListURLMaker(version uint16) string { // The goal here is to update quickly when new versions of the VendorList are released, but not wreck // server performance if a bad CMP starts sending us malformed consent strings that advertize a version // that doesn't exist yet. -func newOccasionalSaver(timeout time.Duration) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { +func newOccasionalSaver(timeout time.Duration, TCFVer uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { lastSaved := &atomic.Value{} lastSaved.Store(time.Time{}) @@ -80,13 +88,13 @@ func newOccasionalSaver(timeout time.Duration) func(ctx context.Context, client if now.Sub(lastSaved.Load().(time.Time)).Minutes() > 10 { withTimeout, cancel := context.WithTimeout(ctx, timeout) defer cancel() - saveOne(withTimeout, client, url, saver) + saveOne(withTimeout, client, url, saver, TCFVer) lastSaved.Store(now) } } } -func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors) uint16 { +func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, cTFVer uint8) uint16 { req, err := http.NewRequest("GET", url, nil) if err != nil { glog.Errorf("Failed to build GET %s request. Cookie syncs may be affected: %v", url, err) @@ -109,8 +117,12 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen glog.Errorf("GET %s returned %d. Cookie syncs may be affected.", url, resp.StatusCode) return 0 } - - newList, err := vendorlist.ParseEagerly(respBody) + var newList api.VendorList + if cTFVer == 2 { + newList, err = vendorlist2.ParseEagerly(respBody) + } else { + newList, err = vendorlist.ParseEagerly(respBody) + } if err != nil { glog.Errorf("GET %s returned malformed JSON. Cookie syncs may be affected. Error was %v. Body was %s", url, err, string(respBody)) return 0 @@ -120,13 +132,13 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return newList.Version() } -func newVendorListCache() (save func(id uint16, list vendorlist.VendorList), load func(id uint16) vendorlist.VendorList) { +func newVendorListCache() (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) { cache := &sync.Map{} - save = func(id uint16, list vendorlist.VendorList) { + save = func(id uint16, list api.VendorList) { cache.Store(id, list) } - load = func(id uint16) vendorlist.VendorList { + load = func(id uint16) api.VendorList { list, ok := cache.Load(id) if ok { return list.(vendorlist.VendorList) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index cdde3c46a68..8197fa263bc 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -29,7 +29,7 @@ func TestVendorFetch(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) list, err := fetcher(context.Background(), 1) assertNilErr(t, err) vendor := list.Vendor(32) @@ -61,7 +61,7 @@ func TestLazyFetch(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) list, err := fetcher(context.Background(), 2) assertNilErr(t, err) @@ -83,7 +83,7 @@ func TestInitialTimeout(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Time{}) defer cancel() - fetcher := newVendorListFetcher(ctx, testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(ctx, testConfig(), server.Client(), testURLMaker(server), 1) _, err := fetcher(context.Background(), 1) // This should do a lazy fetch, even though the initial call failed assertNilErr(t, err) } @@ -106,7 +106,7 @@ func TestFetchThrottling(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) _, err := fetcher(context.Background(), 2) assertNilErr(t, err) _, err = fetcher(context.Background(), 3) @@ -117,7 +117,7 @@ func TestMalformedVendorlistFetch(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{1: "{}"}))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) _, err := fetcher(context.Background(), 1) assertErr(t, err, false) } @@ -126,15 +126,17 @@ func TestMissingVendorlistFetch(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{1: "{}"}))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) _, err := fetcher(context.Background(), 2) assertErr(t, err, false) } func TestVendorListMaker(t *testing.T) { - assertStringsEqual(t, "https://vendorlist.consensu.org/vendorlist.json", vendorListURLMaker(0)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v-2/vendorlist.json", vendorListURLMaker(2)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v-12/vendorlist.json", vendorListURLMaker(12)) + assertStringsEqual(t, "https://vendorlist.consensu.org/vendorlist.json", vendorListURLMaker(0, 1)) + assertStringsEqual(t, "https://vendorlist.consensu.org/v-2/vendorlist.json", vendorListURLMaker(2, 1)) + assertStringsEqual(t, "https://vendorlist.consensu.org/v-12/vendorlist.json", vendorListURLMaker(12, 1)) + assertStringsEqual(t, "https://vendorlist.consensu.org/v2/vendor-list.json", vendorListURLMaker(0, 2)) + assertStringsEqual(t, "https://vendorlist.consensu.org/v2/archives/vendor-list-v7.json", vendorListURLMaker(7, 2)) } // mockServer returns a handler which returns the given response for each global vendor list version. @@ -201,9 +203,9 @@ func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purpos return string(data) } -func testURLMaker(server *httptest.Server) func(uint16) string { +func testURLMaker(server *httptest.Server) func(uint16, uint8) string { url := server.URL - return func(version uint16) string { + return func(version uint16, TCFVer uint8) string { return url + "?version=" + strconv.Itoa(int(version)) } } diff --git a/go.mod b/go.mod index ea1f65efaa4..387b8b9815c 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/onsi/gomega v1.7.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.2.0 // indirect - github.com/prebid/go-gdpr v0.6.0 + github.com/prebid/go-gdpr v0.7.0 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 diff --git a/go.sum b/go.sum index 6d215da0af5..ad9caf5004b 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prebid/go-gdpr v0.6.0 h1:/GKrygGkUbsgd96HIkjAu7/6CHtRedvcojRtfAd4Igc= -github.com/prebid/go-gdpr v0.6.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= +github.com/prebid/go-gdpr v0.7.0 h1:m4E/FjUhTBMciDsd3lQlbzFyXLzNK+JQkFmInJpFAwc= +github.com/prebid/go-gdpr v0.7.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= From 163f33187cebd4dc87087ffd7f67392d73c89dea Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Thu, 26 Mar 2020 09:30:39 -0700 Subject: [PATCH 037/603] Updated price granularity unmarshal to accept empty values and ranges (#1230) --- openrtb_ext/request.go | 7 ++++--- openrtb_ext/request_test.go | 13 ++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index ee1a0cd0f8b..9d1456c9618 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -148,10 +148,11 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { } prevMax = gr.Max } - } else { - return errors.New("Price granularity error: empty granularity definition supplied") + *pg = PriceGranularity(pgraw) + return nil } - *pg = PriceGranularity(pgraw) + // Default to medium if no ranges are specified + *pg = priceGranularityMed return nil } diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 860334af98f..3291c4f9fb2 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -175,11 +175,22 @@ var validGranularityTests []granularityTestData = []granularityTestData{ }, }, }, + { + json: []byte(`{}`), + target: priceGranularityMed, + }, + { + json: []byte(`{"precision": 2}`), + target: priceGranularityMed, + }, + { + json: []byte(`{"precision": 2, "ranges":[]}`), + target: priceGranularityMed, + }, } func TestGranularityUnmarshalBad(t *testing.T) { tests := [][]byte{ - []byte(`{}`), []byte(`[]`), []byte(`{"precision": -1, "ranges": [{"max":20, "increment":0.5}]}`), []byte(`{"ranges":[{"max":20, "increment": -1}]}`), From 469986402cfcd680687fa3fcc2b1c23885aef31c Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Thu, 26 Mar 2020 20:33:30 +0300 Subject: [PATCH 038/603] Update vendorID for TheMediaGrid s2s Bid Adapter (#1232) --- adapters/grid/usersync.go | 2 +- adapters/grid/usersync_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/grid/usersync.go b/adapters/grid/usersync.go index ddf7d5db66b..afdc5db763c 100644 --- a/adapters/grid/usersync.go +++ b/adapters/grid/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGridSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("grid", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("grid", 686, temp, adapters.SyncTypeRedirect) } diff --git a/adapters/grid/usersync_test.go b/adapters/grid/usersync_test.go index 9b97f605a41..99730b5deb4 100644 --- a/adapters/grid/usersync_test.go +++ b/adapters/grid/usersync_test.go @@ -25,6 +25,6 @@ func TestGridSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.EqualValues(t, 686, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } From 7e706f499e66ee8731c23f1b231db3530897c3db Mon Sep 17 00:00:00 2001 From: Aadesh Date: Fri, 27 Mar 2020 10:17:48 -0400 Subject: [PATCH 039/603] treat 204 from FAN as a no bids response (#1233) Co-authored-by: Aadesh Patel --- .../supplemental/no-bid-204.json | 91 +++++++++++++++++++ adapters/audienceNetwork/facebook.go | 6 ++ 2 files changed, 97 insertions(+) create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json new file mode 100644 index 00000000000..042c86bd7fd --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + } + ], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "w": -1, + "h": -1 + }, + "tagid": "123_456" + } + ], + "ext": { + "appnexus": { + "hb_source": 5 + }, + "prebid": {} + }, + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 3ece7bb99e4..db7657f59b7 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -333,6 +333,12 @@ func modifyImpCustom(json []byte, imp *openrtb.Imp) ([]byte, error) { } func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + /* No bid response */ + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + /* Any other http status codes outside of 200 and 204 should be treated as errors */ if response.StatusCode != http.StatusOK { msg := response.Headers.Get("x-fb-an-errors") return nil, []error{&errortypes.BadInput{ From e05369b20aaf542b08bb4c1f9c0e61d289789a3d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 30 Mar 2020 07:48:41 -0700 Subject: [PATCH 040/603] AMP CCPA Fix (#1187) --- analytics/config/testFiles/test-20200303 | 0 endpoints/openrtb2/amp_auction.go | 90 ++- endpoints/openrtb2/amp_auction_test.go | 864 ++++++++++++----------- endpoints/openrtb2/auction.go | 19 +- endpoints/openrtb2/video_auction.go | 6 +- errortypes/code.go | 34 + errortypes/code_test.go | 24 + errortypes/errortypes.go | 101 +-- errortypes/severity.go | 63 ++ errortypes/severity_test.go | 143 ++++ exchange/exchange.go | 14 +- openrtb_ext/bidders.go | 5 + openrtb_ext/bidders_test.go | 20 +- privacy/ccpa/policy.go | 34 +- privacy/ccpa/policy_test.go | 150 +++- privacy/gdpr/policy.go | 7 + privacy/gdpr/policy_test.go | 29 + privacy/policies.go | 25 + privacy/policies_test.go | 42 ++ 19 files changed, 1082 insertions(+), 588 deletions(-) delete mode 100644 analytics/config/testFiles/test-20200303 create mode 100644 errortypes/code.go create mode 100644 errortypes/code_test.go create mode 100644 errortypes/severity.go create mode 100644 errortypes/severity_test.go diff --git a/analytics/config/testFiles/test-20200303 b/analytics/config/testFiles/test-20200303 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8edc1e13787..97ac8d0caae 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -23,8 +23,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" @@ -36,6 +34,7 @@ type AmpResponse struct { Targeting map[string]string `json:"targeting"` Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"errors,omitempty"` + Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"warnings,omitempty"` } // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing @@ -121,13 +120,13 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin") req, errL := deps.parseAmpRequest(r) + ao.Errors = append(ao.Errors, errL...) - if fatalError(errL) { + if errortypes.ContainsFatalError(errL) { w.WriteHeader(http.StatusBadRequest) - for _, err := range errL { + for _, err := range errortypes.FatalOnly(errL) { w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error()))) } - ao.Errors = append(ao.Errors, errL...) labels.RequestStatus = pbsmetrics.RequestStatusBadInput return } @@ -151,18 +150,18 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // Blacklist account now that we have resolved the value if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { errL = append(errL, acctIdErr) - erVal := errortypes.DecodeError(acctIdErr) - if erVal == errortypes.BlacklistedAppCode || erVal == errortypes.BlacklistedAcctCode { + errCode := errortypes.ReadCode(acctIdErr) + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { w.WriteHeader(http.StatusServiceUnavailable) labels.RequestStatus = pbsmetrics.RequestStatusBlacklisted - } else { //erVal == errortypes.AcctRequiredCode + } else { w.WriteHeader(http.StatusBadRequest) labels.RequestStatus = pbsmetrics.RequestStatusBadInput } - for _, err := range errL { + for _, err := range errortypes.FatalOnly(errL) { w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error()))) } - ao.Errors = append(ao.Errors, errL...) + ao.Errors = append(ao.Errors, acctIdErr) return } @@ -206,6 +205,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } } } + // Extract any errors var extResponse openrtb_ext.ExtBidResponse eRErr := json.Unmarshal(response.Ext, &extResponse) @@ -213,10 +213,20 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } + warnings := make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError) + for _, v := range errortypes.WarningOnly(errL) { + bidderErr := openrtb_ext.ExtBidderError{ + Code: errortypes.ReadCode(v), + Message: v.Error(), + } + warnings[openrtb_ext.BidderNameGeneral] = append(warnings[openrtb_ext.BidderNameGeneral], bidderErr) + } + // Now JSONify the targets for the AMP response. ampResponse := AmpResponse{ Targeting: targets, Errors: extResponse.Errors, + Warnings: warnings, } ao.AmpTargetingValues = targets @@ -252,8 +262,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // If the errors list has at least one element, then no guarantees are made about the returned request. func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { // Load the stored request for the AMP ID. - req, errs = deps.loadRequestJSONForAmp(httpRequest) - if len(errs) > 0 { + req, e := deps.loadRequestJSONForAmp(httpRequest) + if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { return } @@ -261,18 +271,15 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr deps.setFieldsImplicitly(httpRequest, req) // Need to ensure cache and targeting are turned on - errs = defaultRequestExt(req) - if len(errs) > 0 { + e = defaultRequestExt(req) + if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { return } // At this point, we should have a valid request that definitely has Targeting and Cache turned on - errL := deps.validateRequest(req) - if len(errL) > 0 { - errs = append(errs, errL...) - } - + e = deps.validateRequest(req) + errs = append(errs, e...) return } @@ -287,9 +294,6 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - debugParam := httpRequest.FormValue("debug") - debug := debugParam == "1" - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) defer cancel() @@ -309,7 +313,8 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - if debug { + debugParam := httpRequest.FormValue("debug") + if debugParam == "1" { req.Test = 1 } @@ -336,18 +341,15 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *req.Imp[0].Secure = 1 } - err := deps.overrideWithParams(httpRequest, req) - if err != nil { - errs = []error{err} - } - + errs = deps.overrideWithParams(httpRequest, req) return } -func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *openrtb.BidRequest) error { +func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *openrtb.BidRequest) []error { if req.Site == nil { req.Site = &openrtb.Site{} } + // Override the stored request sizes with AMP ones, if they exist. if req.Imp[0].Banner != nil { width := parseFormInt(httpRequest, "w", 0) @@ -383,16 +385,17 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope req.Imp[0].TagID = slot } - privacyPolicies := privacy.Policies{ - GDPR: gdpr.Policy{ - Consent: httpRequest.URL.Query().Get("gdpr_consent"), - }, - CCPA: ccpa.Policy{ - Value: httpRequest.URL.Query().Get("us_privacy"), - }, - } - if err := privacyPolicies.Write(req); err != nil { - return err + consent := readConsent(httpRequest.URL) + if consent != "" { + if policies, ok := privacy.ReadPoliciesFromConsent(consent); ok { + if err := policies.Write(req); err != nil { + return []error{err} + } + } else { + return []error{&errortypes.InvalidPrivacyConsent{ + Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + }} + } } if timeout, err := strconv.ParseInt(httpRequest.FormValue("timeout"), 10, 64); err == nil { @@ -533,3 +536,12 @@ func setAmpExt(site *openrtb.Site, value string) { site.Ext = json.RawMessage(`{"amp":` + value + `}`) } } + +func readConsent(url *url.URL) string { + if v := url.Query().Get("consent_string"); v != "" { + return v + } + + // Fallback to 'gdpr_consent' for compatability until it's no longer used by AMP. + return url.Query().Get("gdpr_consent") +} diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 39d1e13c50d..b25d5b0cc8f 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -81,9 +81,8 @@ func TestGoodAmpRequests(t *testing.T) { if response.Debug != nil { t.Errorf("Debug present but not requested") } - if _, ok := response.Errors[openrtb_ext.BidderOpenx]; !ok { - t.Errorf("OpenX error message is not present. (%v)", response.Errors) - } + + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors, "errors") } } @@ -122,357 +121,472 @@ func TestAMPPageInfo(t *testing.T) { assert.Equal(t, "test.somepage.co.uk", exchange.lastRequest.Site.Domain) } -func TestConsentThroughEndpoint(t *testing.T) { - // gdpr consent string that will come inside our http.Request query - const consentString = "BOa71ZYOa71ZYAbABBENA8-AAAAbN7_______9______9uz_Gv_r_f__33e8_39v_h_7_-___m_-3zV4-_lvR11yPA1OrfIrwFhiAw" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that DOESN'T come with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(false, false, "", DigiTurstID) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) +func TestGDPRConsent(t *testing.T) { + consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" + existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" + + digitrust := &openrtb_ext.ExtUserDigiTrust{ + ID: "anyDigitrustID", + KeyV: 1, + Pref: 0, + } + + testCases := []struct { + description string + consent string + userExt *openrtb_ext.ExtUser + nilUser bool + expectedUserExt openrtb_ext.ExtUser + }{ + { + description: "Nil User", + consent: consent, + nilUser: true, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: consent, + }, + }, + { + description: "Nil User Ext", + consent: consent, + userExt: nil, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: consent, + }, + }, + { + description: "Overrides Existing Consent", + consent: consent, + userExt: &openrtb_ext.ExtUser{ + Consent: existingConsent, + }, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: consent, + }, + }, + { + description: "Overrides Existing Consent - With Sibling Data", + consent: consent, + userExt: &openrtb_ext.ExtUser{ + Consent: existingConsent, + DigiTrust: digitrust, + }, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: consent, + DigiTrust: digitrust, + }, + }, + { + description: "Does Not Override Existing Consent If Empty", + consent: "", + userExt: &openrtb_ext.ExtUser{ + Consent: existingConsent, + }, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: existingConsent, + }, + }, } - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), - } + for _, test := range testCases { + // Build Request + bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil) + if err != nil { + t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + } - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + + // Build Exchange Endpoint + mockExchange := &mockAmpExchange{} + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + endpoint, _ := NewAmpEndpoint( + mockExchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + metrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BidderMap, + ) + + // Invoke Endpoint + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s", test.consent), nil) + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) + + // Parse Resonse + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", consentString), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) + // Assert Result + result := mockExchange.lastRequest + if !assert.NotNil(t, result, test.description+":lastRequest") { + return + } + if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { + return + } + if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { + return + } + var ue openrtb_ext.ExtUser + err = json.Unmarshal(result.User.Ext, &ue) + if !assert.NoError(t, err, test.description+":deserialize") { + return + } + assert.Equal(t, test.expectedUserExt, ue, test.description) + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors, test.description+":errors") + assert.Empty(t, response.Warnings, test.description+":warnings") + + // Invoke Endpoint With Legacy Param + requestLegacy := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", test.consent), nil) + responseRecorderLegacy := httptest.NewRecorder() + endpoint(responseRecorderLegacy, requestLegacy, nil) + + // Parse Resonse + var responseLegacy AmpResponse + if err := json.Unmarshal(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User, "Resulting bid request should have a valid User field after passing consent string through endpoint") { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext, "Resulting bid request should have a valid Ext field after passing consent string through endpoint") { - return + // Assert Result With Legacy Param + resultLegacy := mockExchange.lastRequest + if !assert.NotNil(t, resultLegacy, test.description+":legacy:lastRequest") { + return + } + if !assert.NotNil(t, resultLegacy.User, test.description+":legacy:lastRequest.User") { + return + } + if !assert.NotNil(t, resultLegacy.User.Ext, test.description+":legacy:lastRequest.User.Ext") { + return + } + var ueLegacy openrtb_ext.ExtUser + err = json.Unmarshal(resultLegacy.User.Ext, &ueLegacy) + if !assert.NoError(t, err, test.description+":legacy:deserialize") { + return + } + assert.Equal(t, test.expectedUserExt, ueLegacy, test.description+":legacy") + assert.Equal(t, expectedErrorsFromHoldAuction, responseLegacy.Errors, test.description+":legacy:errors") + assert.Empty(t, responseLegacy.Warnings, test.description+":legacy:warnings") } - - // Assert string `consent` is found in the User.Ext at all - assert.NotContainsf(t, fullMarshaledBidRequest, "consent:"+consentString, "Expected bid request to contain consent string %s \n", consentString) - - // Assert the last request has a valid User object with a consent string equal to that on the URL query - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err, "Error unmarshalling last processed request") - - // Assert consent string found in `http.Request` was passed correctly to the `User.Ext` object - assert.Contains(t, string(request.URL.RawQuery), consentString, "http.Request should come with a consent string in its query") - assert.Equal(t, consentString, ue.Consent, "Consent string unsuccessfully passed to bid request through AMP endpoint") - - // Assert other user properties found originally in our bid request such as `DigiTrust` were not overwritten - assert.Equal(t, DigiTurstID, ue.DigiTrust.ID, "Passing GDPR consent through endpoint should not override http.Request ExtUser fields other than consent") } -func TestConsentThroughEndpointNilUser(t *testing.T) { - // gdpr consent string that will come inside our http.Request query - const consentString = "BOa71ZYOa71ZYAbABBENA8-AAAAbN7_______9______9uz_Gv_r_f__33e8_39v_h_7_-___m_-3zV4-_lvR11yPA1OrfIrwFhiAw" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that DOESN'T come with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(true, false, "", DigiTurstID) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } - - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), +func TestCCPAConsent(t *testing.T) { + consent := "1NYN" + existingConsent := "1NNN" + + var gdpr int8 = 1 + + testCases := []struct { + description string + consent string + regsExt *openrtb_ext.ExtRegs + nilRegs bool + expectedRegExt openrtb_ext.ExtRegs + }{ + { + description: "Nil Regs", + consent: consent, + nilRegs: true, + expectedRegExt: openrtb_ext.ExtRegs{ + USPrivacy: consent, + }, + }, + { + description: "Nil Regs Ext", + consent: consent, + regsExt: nil, + expectedRegExt: openrtb_ext.ExtRegs{ + USPrivacy: consent, + }, + }, + { + description: "Overrides Existing Consent", + consent: consent, + regsExt: &openrtb_ext.ExtRegs{ + USPrivacy: existingConsent, + }, + expectedRegExt: openrtb_ext.ExtRegs{ + USPrivacy: consent, + }, + }, + { + description: "Overrides Existing Consent - With Sibling Data", + consent: consent, + regsExt: &openrtb_ext.ExtRegs{ + USPrivacy: existingConsent, + GDPR: &gdpr, + }, + expectedRegExt: openrtb_ext.ExtRegs{ + USPrivacy: consent, + GDPR: &gdpr, + }, + }, + { + description: "Does Not Override Existing Consent If Empty", + consent: "", + regsExt: &openrtb_ext.ExtRegs{ + USPrivacy: existingConsent, + }, + expectedRegExt: openrtb_ext.ExtRegs{ + USPrivacy: existingConsent, + }, + }, } - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} + for _, test := range testCases { + // Build Request + bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt) + if err != nil { + t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + } - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", consentString), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + + // Build Exchange Endpoint + mockExchange := &mockAmpExchange{} + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + endpoint, _ := NewAmpEndpoint( + mockExchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + metrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BidderMap, + ) + + // Invoke Endpoint + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s", test.consent), nil) + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) + + // Parse Resonse + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User, "Resulting bid request should have a valid User field after passing consent string through endpoint") { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext, "Resulting bid request should have a valid User.Ext field after passing consent string through endpoint") { - return + // Assert Result + result := mockExchange.lastRequest + if !assert.NotNil(t, result, test.description+":lastRequest") { + return + } + if !assert.NotNil(t, result.Regs, test.description+":lastRequest.Regs") { + return + } + if !assert.NotNil(t, result.Regs.Ext, test.description+":lastRequest.Regs.Ext") { + return + } + var re openrtb_ext.ExtRegs + err = json.Unmarshal(result.Regs.Ext, &re) + if !assert.NoError(t, err, test.description+":deserialize") { + return + } + assert.Equal(t, test.expectedRegExt, re, test.description) + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + assert.Empty(t, response.Warnings) } - - // Assert string `consent` is found in the User.Ext at all - assert.NotContains(t, fullMarshaledBidRequest, "consent:"+consentString, "This bid request should not contain a consent string. It will be passed the one in the http.Request endpoint") - - // Assert the last request has a valid User object with a consent string equal to that on the URL query - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err, "Error unmarshalling last processed request") - - // Assert consent string found in `http.Request` was passed correctly to the `User.Ext` object - assert.Contains(t, string(request.URL.RawQuery), consentString, "http.Request should come with a consent string in its query") - assert.Equal(t, consentString, ue.Consent, "Consent string unsuccessfully passed to bid request through AMP endpoint") } -func TestConsentThroughEndpointNilUserExt(t *testing.T) { - // gdpr consent string that will come inside our http.Request query - const consentString = "BOa71ZYOa71ZYAbABBENA8-AAAAbN7_______9______9uz_Gv_r_f__33e8_39v_h_7_-___m_-3zV4-_lvR11yPA1OrfIrwFhiAw" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that DOESN'T come with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(false, true, "some-consent-string", DigiTurstID) +func TestNoConsent(t *testing.T) { + // Build Request + bid, err := getTestBidRequest(true, nil, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) } - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), - } - - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + // Build Exchange Endpoint + mockExchange := &mockAmpExchange{} + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) endpoint, _ := NewAmpEndpoint( - exchange, + mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, + metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap, ) - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", consentString), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) - - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User, "Resulting bid request should have a valid User field after passing consent string through endpoint") { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext, "Resulting bid request should have a valid Ext field after passing consent string through endpoint") { - return - } - // Assert string `consent` is found in the User.Ext at all - assert.NotContains(t, fullMarshaledBidRequest, "consent:"+consentString, "This bid request should not contain a consent string. It will be passed the one in the http.Request endpoint") + // Invoke Endpoint + request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) - // Assert the last request has a valid User object with a consent string equal to that on the URL query - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err, "Error unmarshalling last processed request") + // Parse Resonse + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } - // Assert consent string found in `http.Request` was passed correctly to the `User.Ext` object - assert.Contains(t, string(request.URL.RawQuery), consentString, "http.Request should come with a consent string in its query") - assert.Equal(t, consentString, ue.Consent, "Consent string unsuccessfully passed to bid request through AMP endpoint") + // Assert Result + result := mockExchange.lastRequest + assert.NotNil(t, result, "lastRequest") + assert.Nil(t, result.User, "lastRequest.User") + assert.Nil(t, result.Regs, "lastRequest.Regs") + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + assert.Empty(t, response.Warnings) } -func TestSubstituteRequestConsentWithEndpointConsent(t *testing.T) { - // gdpr consent string that will come inside our http.Request query - const consentString = "BOa71ZYOa71ZYAbABBENA8-AAAAbN7_______9______9uz_Gv_r_f__33e8_39v_h_7_-___m_-3zV4-_lvR11yPA1OrfIrwFhiAw" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that comes with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(false, false, "some-consent-string", "digitrustId") +func TestInvalidConsent(t *testing.T) { + // Build Request + bid, err := getTestBidRequest(true, nil, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) } - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), - } - - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + // Build Exchange Endpoint + mockExchange := &mockAmpExchange{} + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) endpoint, _ := NewAmpEndpoint( - exchange, + mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, + metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap, ) - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", consentString), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) - - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User) { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext) { - return - } - // Assert the last request has a valid User object with a consent string equal to that on the URL query - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err) - // Assert consent string found in `http.Request` was passed correctly to the `User.Ext` object - assert.Contains(t, string(request.URL.RawQuery), consentString) - assert.Equal(t, consentString, ue.Consent) + // Invoke Endpoint + invalidConsent := "invalid" + request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) - // Assert other user properties found originally in our bid request such as `DigiTrust` were not overwritten - assert.Equal(t, DigiTurstID, ue.DigiTrust.ID) -} - -func TestDontSubstituteRequestConsentWithBlankEndpointConsent(t *testing.T) { - // Blank gdpr consent string that will come inside our http.Request query - const httpURLConsentString = "" - const PrebidConsentString = "some-consent-string" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that comes with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(false, false, PrebidConsentString, "digitrustId") - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } - - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), + // Parse Resonse + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) } - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} - - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&gdpr_consent=%s", httpURLConsentString), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) - - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User) { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext) { - return + // Assert Result + expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ + openrtb_ext.BidderNameGeneral: { + { + Code: 10001, + Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", + }, + }, } - // Assert the last request has a valid User object with a consent string equal to that on the PBS request - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err) - - // Assert consent string found in the PBS request was passed correctly to the `User.Ext` object - assert.Equal(t, PrebidConsentString, ue.Consent) + result := mockExchange.lastRequest + assert.NotNil(t, result, "lastRequest") + assert.Nil(t, result.User, "lastRequest.User") + assert.Nil(t, result.Regs, "lastRequest.Regs") + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + assert.Equal(t, expectedWarnings, response.Warnings) } -func TestDontSubstituteRequestConsentNoEndpointConsent(t *testing.T) { - // Blank gdpr consent string that will come inside our http.Request query - const PrebidConsentString = "some-consent-string" - const DigiTurstID = "digitrustId" - - // Generate a marshaled openrtb.BidRequest that comes with a gdpr consent string - fullMarshaledBidRequest, err := getTestBidRequest(false, false, PrebidConsentString, "digitrustId") - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } - - stored := map[string]json.RawMessage{ - "1": json.RawMessage(fullMarshaledBidRequest), +func TestNewAndLegacyConsentBothProvided(t *testing.T) { + validConsentGDPR1 := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" + validConsentGDPR2 := "BONV8oqONXwgmADACHENAO7pqzAAppY" + + testCases := []struct { + description string + consent string + consentLegacy string + userExt *openrtb_ext.ExtUser + expectedUserExt openrtb_ext.ExtUser + }{ + { + description: "New Consent Wins", + consent: validConsentGDPR1, + consentLegacy: validConsentGDPR2, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: validConsentGDPR1, + }, + }, + { + description: "New Consent Wins - Reverse", + consent: validConsentGDPR2, + consentLegacy: validConsentGDPR1, + expectedUserExt: openrtb_ext.ExtUser{ + Consent: validConsentGDPR2, + }, + }, } - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - exchange := &mockAmpExchange{} + for _, test := range testCases { + // Build Request + bid, err := getTestBidRequest(false, nil, true, nil) + if err != nil { + t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + } - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - consentStringLessHttpRequest := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1"), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, consentStringLessHttpRequest, nil) + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + + // Build Exchange Endpoint + mockExchange := &mockAmpExchange{} + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + endpoint, _ := NewAmpEndpoint( + mockExchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + metrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BidderMap, + ) + + // Invoke Endpoint + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_string=%s&gdpr_consent=%s", test.consent, test.consentLegacy), nil) + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) + + // Parse Resonse + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "User" field - if !assert.NotNil(t, exchange.lastRequest.User) { - return - } - // Assert our bidRequest had a valid "User.Ext" field - if !assert.NotNil(t, exchange.lastRequest.User.Ext) { - return + // Assert Result + result := mockExchange.lastRequest + if !assert.NotNil(t, result, test.description+":lastRequest") { + return + } + if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { + return + } + if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { + return + } + var ue openrtb_ext.ExtUser + err = json.Unmarshal(result.User.Ext, &ue) + if !assert.NoError(t, err, test.description+":deserialize") { + return + } + assert.Equal(t, test.expectedUserExt, ue, test.description) + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + assert.Empty(t, response.Warnings) } - // Assert the last request has a valid User object with a consent string equal to that on the PBS request - var ue openrtb_ext.ExtUser - err = json.Unmarshal(exchange.lastRequest.User.Ext, &ue) - assert.NoError(t, err) - - // Assert consent string found in the PBS request was passed correctly to the `User.Ext` object - assert.Equal(t, PrebidConsentString, ue.Consent) } func TestAMPSiteExt(t *testing.T) { @@ -739,102 +853,6 @@ func TestWidthOnly(t *testing.T) { }.execute(t) } -func TestCCPAPresent(t *testing.T) { - req, err := getTestBidRequest(false, false, "", "digitrustId") - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } - - reqStored := map[string]json.RawMessage{ - "1": json.RawMessage(req), - } - - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - - exchange := &mockAmpExchange{} - - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{reqStored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - - usPrivacy := "1YYN" - httpReq := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&us_privacy="+usPrivacy, nil) - httpRecorder := httptest.NewRecorder() - endpoint(httpRecorder, httpReq, nil) - - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", httpRecorder.Code, httpRecorder.Body.String()) { - return - } - // Assert our bidRequest had a valid "Regs" field - if !assert.NotNil(t, exchange.lastRequest.Regs) { - return - } - // Assert our bidRequest had a valid "Regs.Ext" field - if !assert.NotNil(t, exchange.lastRequest.Regs.Ext) { - return - } - - var regs openrtb_ext.ExtRegs - err = json.Unmarshal(exchange.lastRequest.Regs.Ext, ®s) - assert.NoError(t, err) - assert.Equal(t, usPrivacy, regs.USPrivacy) -} - -func TestCCPANotPresent(t *testing.T) { - req, err := getTestBidRequest(false, false, "", "digitrustId") - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } - - reqStored := map[string]json.RawMessage{ - "1": json.RawMessage(req), - } - - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - - exchange := &mockAmpExchange{} - - endpoint, _ := NewAmpEndpoint( - exchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{reqStored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BidderMap, - ) - - httpReq := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) - httpRecorder := httptest.NewRecorder() - endpoint(httpRecorder, httpReq, nil) - - // Assert our bidRequest was valid - if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", httpRecorder.Code, httpRecorder.Body.String()) { - return - } - - // Assert CCPA Signal Not Found - if exchange.lastRequest.Regs != nil && exchange.lastRequest.Regs.Ext != nil { - var regs openrtb_ext.ExtRegs - err = json.Unmarshal(exchange.lastRequest.Regs.Ext, ®s) - assert.NoError(t, err) - assert.Empty(t, regs.USPrivacy) - } -} - type formatOverrideSpec struct { width uint64 height uint64 @@ -902,6 +920,15 @@ type mockAmpExchange struct { lastRequest *openrtb.BidRequest } +var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ + openrtb_ext.BidderName("openx"): { + { + Code: 1, + Message: "The request exceeded the timeout allocated", + }, + }, +} + func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest @@ -926,39 +953,7 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.B return response, nil } -func getTestBidRequest(nilUser bool, nilExt bool, consentString string, digitrustID string) ([]byte, error) { - var userExt openrtb_ext.ExtUser - var userExtData []byte - var err error - - if consentString != "" { - userExt = openrtb_ext.ExtUser{ - Consent: consentString, - DigiTrust: &openrtb_ext.ExtUserDigiTrust{ - ID: digitrustID, - KeyV: 1, - Pref: 0, - }, - } - } else { - userExt = openrtb_ext.ExtUser{ - DigiTrust: &openrtb_ext.ExtUserDigiTrust{ - ID: digitrustID, - KeyV: 1, - Pref: 0, - }, - } - } - - if !nilExt { - userExtData, err = json.Marshal(userExt) - if err != nil { - return nil, err - } - } else { - userExtData = []byte("") - } - +func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { var width uint64 = 300 var height uint64 = 300 bidRequest := &openrtb.BidRequest{ @@ -988,6 +983,16 @@ func getTestBidRequest(nilUser bool, nilExt bool, consentString string, digitrus Page: "some-page", }, } + + var userExtData []byte + if userExt != nil { + var err error + userExtData, err = json.Marshal(userExt) + if err != nil { + return nil, err + } + } + if !nilUser { bidRequest.User = &openrtb.User{ ID: "aUserId", @@ -995,5 +1000,22 @@ func getTestBidRequest(nilUser bool, nilExt bool, consentString string, digitrus Ext: userExtData, } } + + var regsExtData []byte + if regsExt != nil { + var err error + regsExtData, err = json.Marshal(regsExt) + if err != nil { + return nil, err + } + } + + if !nilRegs { + bidRequest.Regs = &openrtb.Regs{ + COPPA: 1, + Ext: regsExtData, + } + } + return json.Marshal(bidRequest) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d9c31eca98c..4594a4d5f64 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -106,7 +106,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http req, errL := deps.parseRequest(r) - if fatalError(errL) && writeError(errL, w, &labels) { + if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } @@ -326,7 +326,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { if len(errs) > 0 { errL = append(errL, errs...) } - if fatalError(errs) { + if errortypes.ContainsFatalError(errs) { return errL } } @@ -1177,8 +1177,8 @@ func writeError(errs []error, w http.ResponseWriter, labels *pbsmetrics.Labels) httpStatus := http.StatusBadRequest metricsStatus := pbsmetrics.RequestStatusBadInput for _, err := range errs { - erVal := errortypes.DecodeError(err) - if erVal == errortypes.BlacklistedAppCode || erVal == errortypes.BlacklistedAcctCode { + erVal := errortypes.ReadCode(err) + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { httpStatus = http.StatusServiceUnavailable metricsStatus = pbsmetrics.RequestStatusBlacklisted break @@ -1194,17 +1194,6 @@ func writeError(errs []error, w http.ResponseWriter, labels *pbsmetrics.Labels) return rc } -// Checks to see if an error in an error list is a fatal error -func fatalError(errL []error) bool { - for _, err := range errL { - errCode := errortypes.DecodeError(err) - if errCode != errortypes.BidderTemporarilyDisabledCode && errCode != errortypes.WarningCode { - return true - } - } - return false -} - // Returns the effective publisher ID func effectivePubID(pub *openrtb.Publisher) string { if pub != nil { diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 630a3f5acd3..0215eb4cff2 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -298,12 +298,12 @@ func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, var errors string var status int = http.StatusInternalServerError for _, er := range errL { - erVal := errortypes.DecodeError(er) - if erVal == errortypes.BlacklistedAppCode || erVal == errortypes.BlacklistedAcctCode { + erVal := errortypes.ReadCode(er) + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { status = http.StatusServiceUnavailable labels.RequestStatus = pbsmetrics.RequestStatusBlacklisted break - } else if erVal == errortypes.AcctRequiredCode { + } else if erVal == errortypes.AcctRequiredErrorCode { status = http.StatusBadRequest labels.RequestStatus = pbsmetrics.RequestStatusBadInput break diff --git a/errortypes/code.go b/errortypes/code.go new file mode 100644 index 00000000000..80a5eb45542 --- /dev/null +++ b/errortypes/code.go @@ -0,0 +1,34 @@ +package errortypes + +// Defines numeric codes for well-known errors. +const ( + UnknownErrorCode = 999 + TimeoutErrorCode = iota + BadInputErrorCode + BlacklistedAppErrorCode + BadServerResponseErrorCode + FailedToRequestBidsErrorCode + BidderTemporarilyDisabledErrorCode + BlacklistedAcctErrorCode + AcctRequiredErrorCode +) + +// Defines numeric codes for well-known warnings. +const ( + UnknownWarningCode = 10999 + InvalidPrivacyConsentWarningCode = iota + 10000 +) + +// Coder provides an error or warning code with severity. +type Coder interface { + Code() int + Severity() Severity +} + +// ReadCode returns the error or warning code, or UnknownErrorCode if unavailable. +func ReadCode(err error) int { + if e, ok := err.(Coder); ok { + return e.Code() + } + return UnknownErrorCode +} diff --git a/errortypes/code_test.go b/errortypes/code_test.go new file mode 100644 index 00000000000..b2bf53b8340 --- /dev/null +++ b/errortypes/code_test.go @@ -0,0 +1,24 @@ +package errortypes + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadCodeWithCodeDefined(t *testing.T) { + err := &Timeout{Message: "code is defined"} + + result := ReadCode(err) + + assert.Equal(t, result, TimeoutErrorCode) +} + +func TestReadCodeWithCodeNotDefined(t *testing.T) { + err := errors.New("missing error code") + + result := ReadCode(err) + + assert.Equal(t, result, UnknownErrorCode) +} diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 4bcea24f737..c953f9b7e08 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -1,28 +1,5 @@ package errortypes -// These define the error codes for all the errors enumerated in this package -// NoErrorCode is to reserve 0 for non error states. -const ( - NoErrorCode = iota - TimeoutCode - BadInputCode - BlacklistedAppCode - BadServerResponseCode - FailedToRequestBidsCode - BidderTemporarilyDisabledCode - BlacklistedAcctCode - AcctRequiredCode - WarningCode -) - -// We should use this code for any Error interface that is not in this package -const UnknownErrorCode = 999 - -// Coder provides an interface to use if we want to check the code of an error type created in this package. -type Coder interface { - Code() int -} - // Timeout should be used to flag that a bidder failed to return a response because the PBS timeout timer // expired before a result was received. // @@ -36,7 +13,11 @@ func (err *Timeout) Error() string { } func (err *Timeout) Code() int { - return TimeoutCode + return TimeoutErrorCode +} + +func (err *Timeout) Severity() Severity { + return SeverityFatal } // BadInput should be used when returning errors which are caused by bad input. @@ -52,7 +33,11 @@ func (err *BadInput) Error() string { } func (err *BadInput) Code() int { - return BadInputCode + return BadInputErrorCode +} + +func (err *BadInput) Severity() Severity { + return SeverityFatal } // BlacklistedApp should be used when a request App.ID matches an entry in the BlacklistedApps @@ -68,7 +53,11 @@ func (err *BlacklistedApp) Error() string { } func (err *BlacklistedApp) Code() int { - return BlacklistedAppCode + return BlacklistedAppErrorCode +} + +func (err *BlacklistedApp) Severity() Severity { + return SeverityFatal } // BlacklistedAcct should be used when a request account ID matches an entry in the BlacklistedAccts @@ -84,7 +73,11 @@ func (err *BlacklistedAcct) Error() string { } func (err *BlacklistedAcct) Code() int { - return BlacklistedAcctCode + return BlacklistedAcctErrorCode +} + +func (err *BlacklistedAcct) Severity() Severity { + return SeverityFatal } // AcctRequired should be used when the environment variable ACCOUNT_REQUIRED has been set to not @@ -100,7 +93,11 @@ func (err *AcctRequired) Error() string { } func (err *AcctRequired) Code() int { - return AcctRequiredCode + return AcctRequiredErrorCode +} + +func (err *AcctRequired) Severity() Severity { + return SeverityFatal } // BadServerResponse should be used when returning errors which are caused by bad/unexpected behavior on the remote server. @@ -121,7 +118,11 @@ func (err *BadServerResponse) Error() string { } func (err *BadServerResponse) Code() int { - return BadServerResponseCode + return BadServerResponseErrorCode +} + +func (err *BadServerResponse) Severity() Severity { + return SeverityFatal } // FailedToRequestBids is an error to cover the case where an adapter failed to generate any http requests to get bids, @@ -138,7 +139,11 @@ func (err *FailedToRequestBids) Error() string { } func (err *FailedToRequestBids) Code() int { - return FailedToRequestBidsCode + return FailedToRequestBidsErrorCode +} + +func (err *FailedToRequestBids) Severity() Severity { + return SeverityFatal } // BidderTemporarilyDisabled is used at the request validation step, where we want to continue processing as best we @@ -153,10 +158,14 @@ func (err *BidderTemporarilyDisabled) Error() string { } func (err *BidderTemporarilyDisabled) Code() int { - return BidderTemporarilyDisabledCode + return BidderTemporarilyDisabledErrorCode +} + +func (err *BidderTemporarilyDisabled) Severity() Severity { + return SeverityWarning } -// Warning is a generic warning type, not a serious error +// Warning is a generic non-fatal error. type Warning struct { Message string } @@ -165,15 +174,27 @@ func (err *Warning) Error() string { return err.Message } -// Code returns the error code func (err *Warning) Code() int { - return WarningCode + return UnknownWarningCode +} + +func (err *Warning) Severity() Severity { + return SeverityWarning +} + +// InvalidPrivacyConsent is a warning for when the privacy consent string is invalid and is ignored. +type InvalidPrivacyConsent struct { + Message string +} + +func (err *InvalidPrivacyConsent) Error() string { + return err.Message +} + +func (err *InvalidPrivacyConsent) Code() int { + return InvalidPrivacyConsentWarningCode } -// DecodeError provides the error code for an error, as defined above -func DecodeError(err error) int { - if ce, ok := err.(Coder); ok { - return ce.Code() - } - return UnknownErrorCode +func (err *InvalidPrivacyConsent) Severity() Severity { + return SeverityWarning } diff --git a/errortypes/severity.go b/errortypes/severity.go new file mode 100644 index 00000000000..0838b09592e --- /dev/null +++ b/errortypes/severity.go @@ -0,0 +1,63 @@ +package errortypes + +// Severity represents the severity level of a bid processing error. +type Severity int + +const ( + // SeverityUnknown represents an unknown severity level. + SeverityUnknown Severity = iota + + // SeverityFatal represents a fatal bid processing error which prevents a bid response. + SeverityFatal + + // SeverityWarning represents a non-fatal bid processing error where invalid or ambiguous + // data in the bid request was ignored. + SeverityWarning +) + +func isFatal(err error) bool { + s, ok := err.(Coder) + return !ok || s.Severity() == SeverityFatal +} + +func isWarning(err error) bool { + s, ok := err.(Coder) + return ok && s.Severity() == SeverityWarning +} + +// ContainsFatalError checks if the error list contains a fatal error. +func ContainsFatalError(errors []error) bool { + for _, err := range errors { + if isFatal(err) { + return true + } + } + + return false +} + +// FatalOnly returns a new error list with only the fatal severity errors. +func FatalOnly(errs []error) []error { + errsFatal := make([]error, 0, len(errs)) + + for _, err := range errs { + if isFatal(err) { + errsFatal = append(errsFatal, err) + } + } + + return errsFatal +} + +// WarningOnly returns a new error list with only the warning severity errors. +func WarningOnly(errs []error) []error { + errsWarning := make([]error, 0, len(errs)) + + for _, err := range errs { + if isWarning(err) { + errsWarning = append(errsWarning, err) + } + } + + return errsWarning +} diff --git a/errortypes/severity_test.go b/errortypes/severity_test.go new file mode 100644 index 00000000000..8330316a8d2 --- /dev/null +++ b/errortypes/severity_test.go @@ -0,0 +1,143 @@ +package errortypes + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +type stubError struct{ severity Severity } + +func (e *stubError) Error() string { return "anyMessage" } +func (e *stubError) Code() int { return 42 } +func (e *stubError) Severity() Severity { return e.severity } + +func TestContainsFatalError(t *testing.T) { + fatalError := &stubError{severity: SeverityFatal} + notFatalError := &stubError{severity: SeverityWarning} + unknownSeverityError := errors.New("anyError") + + testCases := []struct { + description string + errors []error + shouldBeFatal bool + }{ + { + description: "None", + errors: []error{}, + shouldBeFatal: false, + }, + { + description: "One - Fatal", + errors: []error{fatalError}, + shouldBeFatal: true, + }, + { + description: "One - Not Fatal", + errors: []error{notFatalError}, + shouldBeFatal: false, + }, + { + description: "One - Unknown Severity Same As Fatal", + errors: []error{unknownSeverityError}, + shouldBeFatal: true, + }, + { + description: "Mixed", + errors: []error{fatalError, notFatalError, unknownSeverityError}, + shouldBeFatal: true, + }, + } + + for _, tc := range testCases { + result := ContainsFatalError(tc.errors) + assert.Equal(t, tc.shouldBeFatal, result) + } +} + +func TestFatalOnly(t *testing.T) { + fatalError := &stubError{severity: SeverityFatal} + notFatalError := &stubError{severity: SeverityWarning} + unknownSeverityError := errors.New("anyError") + + testCases := []struct { + description string + errs []error + errsShouldBeFatal []error + }{ + { + description: "None", + errs: []error{}, + errsShouldBeFatal: []error{}, + }, + { + description: "One - Fatal", + errs: []error{fatalError}, + errsShouldBeFatal: []error{fatalError}, + }, + { + description: "One - Not Fatal", + errs: []error{notFatalError}, + errsShouldBeFatal: []error{}, + }, + { + description: "One - Unknown Severity Same As Fatal", + errs: []error{unknownSeverityError}, + errsShouldBeFatal: []error{unknownSeverityError}, + }, + { + description: "Mixed", + errs: []error{fatalError, notFatalError, unknownSeverityError}, + errsShouldBeFatal: []error{fatalError, unknownSeverityError}, + }, + } + + for _, tc := range testCases { + result := FatalOnly(tc.errs) + assert.ElementsMatch(t, tc.errsShouldBeFatal, result) + } +} + +func TestWarningOnly(t *testing.T) { + warningError := &stubError{severity: SeverityWarning} + notWarningError := &stubError{severity: SeverityFatal} + unknownSeverityError := errors.New("anyError") + + testCases := []struct { + description string + errs []error + errsShouldBeWarning []error + }{ + { + description: "None", + errs: []error{}, + errsShouldBeWarning: []error{}, + }, + { + description: "One - Warning", + errs: []error{warningError}, + errsShouldBeWarning: []error{warningError}, + }, + { + description: "One - Not Warning", + errs: []error{notWarningError}, + errsShouldBeWarning: []error{}, + }, + { + description: "One - Unknown Severity Not Warning", + errs: []error{unknownSeverityError}, + errsShouldBeWarning: []error{}, + }, + { + description: "One - Mixed", + errs: []error{warningError, notWarningError, unknownSeverityError}, + errsShouldBeWarning: []error{warningError}, + }, + } + + for _, tc := range testCases { + result := WarningOnly(tc.errs) + assert.ElementsMatch(t, tc.errsShouldBeWarning, result) + } +} diff --git a/exchange/exchange.go b/exchange/exchange.go index 995add3d496..3cab1880456 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -385,14 +385,14 @@ func errorsToMetric(errs []error) map[pbsmetrics.AdapterError]struct{} { ret := make(map[pbsmetrics.AdapterError]struct{}, len(errs)) var s struct{} for _, err := range errs { - switch errortypes.DecodeError(err) { - case errortypes.TimeoutCode: + switch errortypes.ReadCode(err) { + case errortypes.TimeoutErrorCode: ret[pbsmetrics.AdapterErrorTimeout] = s - case errortypes.BadInputCode: + case errortypes.BadInputErrorCode: ret[pbsmetrics.AdapterErrorBadInput] = s - case errortypes.BadServerResponseCode: + case errortypes.BadServerResponseErrorCode: ret[pbsmetrics.AdapterErrorBadServerResponse] = s - case errortypes.FailedToRequestBidsCode: + case errortypes.FailedToRequestBidsErrorCode: ret[pbsmetrics.AdapterErrorFailedToRequestBids] = s default: ret[pbsmetrics.AdapterErrorUnknown] = s @@ -404,7 +404,7 @@ func errorsToMetric(errs []error) map[pbsmetrics.AdapterError]struct{} { func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { serr := make([]openrtb_ext.ExtBidderError, len(errs)) for i := 0; i < len(errs); i++ { - serr[i].Code = errortypes.DecodeError(errs[i]) + serr[i].Code = errortypes.ReadCode(errs[i]) serr[i].Message = errs[i].Error() } return serr @@ -672,7 +672,7 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B ext, err := json.Marshal(sbExt) if err != nil { extError := openrtb_ext.ExtBidderError{ - Code: errortypes.DecodeError(err), + Code: errortypes.ReadCode(err), Message: fmt.Sprintf("Error writing SeatBid.Ext: %s", err.Error()), } adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, extError) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 00c25f8a3f0..e3f186db333 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -17,8 +17,12 @@ const schemaDirectory = "static/bidder-params" // BidderName may refer to a bidder ID, or an Alias which is defined in the request. type BidderName string +// BidderNameGeneral is reserved for non-bidder specific messages when using a map keyed on the bidder name. +const BidderNameGeneral = BidderName("general") + // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project. // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions. +// The bidder name 'general' is not allowed since it has special meaning in message maps. const ( Bidder33Across BidderName = "33across" BidderAdform BidderName = "adform" @@ -80,6 +84,7 @@ const ( ) // BidderMap stores all the valid OpenRTB 2.x Bidders in the project. This map *must not* be mutated. +// The bidder name 'general' is not allowed since it has special meaning in message maps. var BidderMap = map[string]BidderName{ "33across": Bidder33Across, "adform": BidderAdform, diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 454a2454f31..d49b23237ed 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/xeipuuv/gojsonschema" ) @@ -49,21 +50,14 @@ func TestInvalidParams(t *testing.T) { } } -func TestBidderList(t *testing.T) { - list := BidderList() +func TestBidderListMatchesBidderMap(t *testing.T) { + bidders := BidderList() for _, bidderName := range BidderMap { - adapterInList(t, bidderName, list) + assert.Contains(t, bidders, bidderName) } } -func adapterInList(t *testing.T, a BidderName, l []BidderName) { - found := false - for _, n := range l { - if a == n { - found = true - } - } - if !found { - t.Errorf("Adapter %s not found in the adapter map!", a) - } +func TestBidderListDoesNotDefineGeneral(t *testing.T) { + bidders := BidderList() + assert.NotContains(t, bidders, BidderNameGeneral) } diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index e34a35717a4..8b50e1112a9 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -3,6 +3,7 @@ package ccpa import ( "encoding/json" "errors" + "fmt" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" @@ -49,35 +50,44 @@ func (p Policy) Write(req *openrtb.BidRequest) error { return err } -// Validate returns an error if the CCPA regulation value does not adhere to the IAB spec. +// Validate returns an error if the CCPA policy does not adhere to the IAB spec. func (p Policy) Validate() error { - if p.Value == "" { + if err := ValidateConsent(p.Value); err != nil { + return fmt.Errorf("request.regs.ext.us_privacy %s", err.Error()) + } + + return nil +} + +// ValidateConsent returns an error if the CCPA consent string does not adhere to the IAB spec. +func ValidateConsent(consent string) error { + if consent == "" { return nil } - if len(p.Value) != 4 { - return errors.New("request.regs.ext.us_privacy must contain 4 characters") + if len(consent) != 4 { + return errors.New("must contain 4 characters") } - if p.Value[0] != '1' { - return errors.New("request.regs.ext.us_privacy must specify version 1") + if consent[0] != '1' { + return errors.New("must specify version 1") } var c byte - c = p.Value[1] + c = consent[1] if c != 'N' && c != 'Y' && c != '-' { - return errors.New("request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice") + return errors.New("must specify 'N', 'Y', or '-' for the explicit notice") } - c = p.Value[2] + c = consent[2] if c != 'N' && c != 'Y' && c != '-' { - return errors.New("request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale") + return errors.New("must specify 'N', 'Y', or '-' for the opt-out sale") } - c = p.Value[3] + c = consent[3] if c != 'N' && c != 'Y' && c != '-' { - return errors.New("request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement") + return errors.New("must specify 'N', 'Y', or '-' for the limited service provider agreement") } return nil diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index a70874ebbec..54613c89880 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -154,74 +154,148 @@ func TestWrite(t *testing.T) { func TestValidate(t *testing.T) { testCases := []struct { - description string - policy Policy - expected string + description string + policy Policy + expectedError string }{ { - description: "Valid", - policy: Policy{Value: "1NYN"}, - expected: "", + description: "Valid", + policy: Policy{Value: "1NYN"}, + expectedError: "", }, { - description: "Valid - Not Applicable", - policy: Policy{Value: "1---"}, - expected: "", + description: "Valid - Not Applicable", + policy: Policy{Value: "1---"}, + expectedError: "", }, { - description: "Valid - Empty", - policy: Policy{Value: ""}, - expected: "", + description: "Valid - Empty", + policy: Policy{Value: ""}, + expectedError: "", }, { - description: "Invalid Length", - policy: Policy{Value: "1NY"}, - expected: "request.regs.ext.us_privacy must contain 4 characters", + description: "Invalid Length", + policy: Policy{Value: "1NY"}, + expectedError: "request.regs.ext.us_privacy must contain 4 characters", }, { - description: "Invalid Version", - policy: Policy{Value: "2---"}, - expected: "request.regs.ext.us_privacy must specify version 1", + description: "Invalid Version", + policy: Policy{Value: "2---"}, + expectedError: "request.regs.ext.us_privacy must specify version 1", }, { - description: "Invalid Explicit Notice Char", - policy: Policy{Value: "1X--"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", + description: "Invalid Explicit Notice Char", + policy: Policy{Value: "1X--"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", }, { - description: "Invalid Explicit Notice Case", - policy: Policy{Value: "1y--"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", + description: "Invalid Explicit Notice Case", + policy: Policy{Value: "1y--"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", }, { - description: "Invalid Opt-Out Sale Char", - policy: Policy{Value: "1-X-"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Invalid Opt-Out Sale Char", + policy: Policy{Value: "1-X-"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", }, { - description: "Invalid Opt-Out Sale Case", - policy: Policy{Value: "1-y-"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Invalid Opt-Out Sale Case", + policy: Policy{Value: "1-y-"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", }, { - description: "Invalid LSPA Char", - policy: Policy{Value: "1--X"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Invalid LSPA Char", + policy: Policy{Value: "1--X"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", }, { - description: "Invalid LSPA Case", - policy: Policy{Value: "1--y"}, - expected: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Invalid LSPA Case", + policy: Policy{Value: "1--y"}, + expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", }, } for _, test := range testCases { result := test.policy.Validate() - if test.expected == "" { + if test.expectedError == "" { + assert.NoError(t, result, test.description) + } else { + assert.EqualError(t, result, test.expectedError, test.description) + } + } +} + +func TestValidateConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expectedError string + }{ + { + description: "Valid", + consent: "1NYN", + expectedError: "", + }, + { + description: "Valid - Not Applicable", + consent: "1---", + expectedError: "", + }, + { + description: "Invalid Empty", + consent: "", + expectedError: "", + }, + { + description: "Invalid Length", + consent: "1NY", + expectedError: "must contain 4 characters", + }, + { + description: "Invalid Version", + consent: "2---", + expectedError: "must specify version 1", + }, + { + description: "Invalid Explicit Notice Char", + consent: "1X--", + expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + }, + { + description: "Invalid Explicit Notice Case", + consent: "1y--", + expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + }, + { + description: "Invalid Opt-Out Sale Char", + consent: "1-X-", + expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + }, + { + description: "Invalid Opt-Out Sale Case", + consent: "1-y-", + expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + }, + { + description: "Invalid LSPA Char", + consent: "1--X", + expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + }, + { + description: "Invalid LSPA Case", + consent: "1--y", + expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + }, + } + + for _, test := range testCases { + result := ValidateConsent(test.consent) + + if test.expectedError == "" { assert.NoError(t, result, test.description) } else { - assert.EqualError(t, result, test.expected, test.description) + assert.EqualError(t, result, test.expectedError, test.description) } } } diff --git a/privacy/gdpr/policy.go b/privacy/gdpr/policy.go index 61f95ac99c6..a4e1bc6aac7 100644 --- a/privacy/gdpr/policy.go +++ b/privacy/gdpr/policy.go @@ -5,6 +5,7 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" + "github.com/prebid/go-gdpr/vendorconsent" ) // Policy represents the GDPR regulation for an OpenRTB bid request. @@ -32,3 +33,9 @@ func (p Policy) Write(req *openrtb.BidRequest) error { req.User.Ext, err = jsonparser.Set(req.User.Ext, []byte(`"`+p.Consent+`"`), "consent") return err } + +// ValidateConsent returns an error if the GDPR consent string does not adhere to the IAB TCF spec. +func ValidateConsent(consent string) error { + _, err := vendorconsent.ParseString(consent) + return err +} diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index 80bd882dada..00b97644971 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -72,3 +72,32 @@ func TestWrite(t *testing.T) { } } } + +func TestValidateConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expectError bool + }{ + { + description: "Invalid", + consent: "", + expectError: true, + }, + { + description: "Valid", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + expectError: false, + }, + } + + for _, test := range testCases { + result := ValidateConsent(test.consent) + + if test.expectError { + assert.Error(t, result, test.description) + } else { + assert.NoError(t, result, test.description) + } + } +} diff --git a/privacy/policies.go b/privacy/policies.go index ebe34ef5c3d..cb11c6d03a6 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -33,3 +33,28 @@ func writePolicies(req *openrtb.BidRequest, writers []policyWriter) error { return nil } + +// ReadPoliciesFromConsent inspects the consent string kind and sets the corresponding values in a new Policies object. +func ReadPoliciesFromConsent(consent string) (Policies, bool) { + if len(consent) == 0 { + return Policies{}, false + } + + if err := gdpr.ValidateConsent(consent); err == nil { + return Policies{ + GDPR: gdpr.Policy{ + Consent: consent, + }, + }, true + } + + if err := ccpa.ValidateConsent(consent); err == nil { + return Policies{ + CCPA: ccpa.Policy{ + Value: consent, + }, + }, true + } + + return Policies{}, false +} diff --git a/privacy/policies_test.go b/privacy/policies_test.go index 697942521fc..34fbe52d0e9 100644 --- a/privacy/policies_test.go +++ b/privacy/policies_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -75,3 +77,43 @@ func (m *mockPolicyWriter) Write(req *openrtb.BidRequest) error { args := m.Called(req) return args.Error(0) } + +func TestReadPoliciesFromConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expectedResultValue Policies + expectedResultOK bool + }{ + { + description: "Empty String", + consent: "", + expectedResultValue: Policies{}, + expectedResultOK: false, + }, + { + description: "CCPA", + consent: "1NYN", + expectedResultValue: Policies{CCPA: ccpa.Policy{Value: "1NYN"}}, + expectedResultOK: true, + }, + { + description: "GDPR TCF 1.0", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + expectedResultValue: Policies{GDPR: gdpr.Policy{Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY"}}, + expectedResultOK: true, + }, + { + description: "Invalid", + consent: "any invalid", + expectedResultValue: Policies{}, + expectedResultOK: false, + }, + } + + for _, test := range testCases { + resultValue, resultOK := ReadPoliciesFromConsent(test.consent) + assert.Equal(t, test.expectedResultValue, resultValue, test.description+":value") + assert.Equal(t, test.expectedResultOK, resultOK, test.description+":ok") + } +} From 7c009bac4f43b9c3b94671ccce58fe2f96024478 Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 30 Mar 2020 16:29:14 -0400 Subject: [PATCH 041/603] Update rubicon.md (#1234) --- docs/bidders/rubicon.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bidders/rubicon.md b/docs/bidders/rubicon.md index 564acd9a3c4..ea376da427d 100644 --- a/docs/bidders/rubicon.md +++ b/docs/bidders/rubicon.md @@ -1,6 +1,6 @@ # Rubicon Bidder -Please contact your Rubicon Project account manager to get set up with a login and cookie-sync URL to run your own Prebid Server. You will be given instructions, including the available endpoints. +Please contact your Rubicon Project account manager or globalsupport@rubiconproject.com to get set up with a login and cookie-sync URL to run your own Prebid Server. You will be given instructions, including the available endpoints. **Note:** Rubicon is disabled by default. Please enable it in the app config if you wish to use it. Make sure you provide the correct cookie-sync URL in order for cookie-syncs to work properly. From 4b1f3e707ff9396517e87f2a488ba54b9c75c098 Mon Sep 17 00:00:00 2001 From: bretg Date: Wed, 1 Apr 2020 15:35:52 -0400 Subject: [PATCH 042/603] adding schain interface (#1203) --- docs/endpoints/openrtb2/auction.md | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index 7795ef5afe0..0f03960190d 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -403,28 +403,21 @@ Example: PBS receiving a request for an interstitial imp and these parameters set, it will rewrite the format object within the interstitial imp. If the format array's first object is a size, PBS will take it as the max size for the interstitial. If that size is 1x1, it will look up the device's size and use that as the max size. If the format is not present, it will also use the device size as the max size. (1x1 support so that you don't have to omit the format object to use the device size) PBS with interstitial support will come preconfigured with a list of common ad sizes. Preferentially organized by weighing the larger and more common sizes first. But no guarantees to the ordering will be made. PBS will generate a new format list for the interstitial imp by traversing this list and picking the first 10 sizes that fall within the imp's max size and minimum percentage size. There will be no attempt to favor aspect ratios closer to the original size's aspect ratio. The limit of 10 is enforced to ensure we don't overload bidders with an overlong list. All the interstitial parameters will still be passed to the bidders, so they may recognize them and use their own size matching algorithms if they prefer. -#### Currency Support +#### Supply Chain Support -To set the desired 'ad server currency', use the standard OpenRTB `cur` attribute. Note that Prebid Server only looks at the first currency in the array. -``` -"cur": ["USD"] -``` +Basic supply chains are passed to Prebid Server on `source.ext.schain` and passed through to bid adapters. Prebid Server does not currently offer the ability to add a node to the supply chain. -If you want or need to define currency conversion rates (e.g. for currencies that your Prebid Server doesn't support), define ext.prebid.currency.rates. (Currently supported in PBS-Java only) +Bidder-specific schains (PBS-Java only): ``` -"ext": { - "prebid": { - "currency": { - "rates": { - "USD": { "UAH": 24.47, "ETB": 32.04 } - } - } - } -} +ext.prebid.schains: [ + { bidders: ["bidderA"], schain: { SCHAIN OBJECT 1}}, + { bidders: ["*"], schain: { SCHAIN OBJECT 2}} +] ``` +In this scenario, Prebid Server sends the first schain object to `bidderA` and the second schain object to everyone else. -If it exists, a rate defined in ext.prebid.currency.rates has the highest priority. If a currency rate doesn't exist in the request, the external file will be used. +If there's already an source.ext.schain and a bidder is named in ext.prebid.schains (or covered by the wildcard condition), ext.prebid.schains takes precedent. #### Stored Responses (PBS-Java only) From 40f433bfc30b47ae32fc302de1b64ef0cbcab086 Mon Sep 17 00:00:00 2001 From: bretg Date: Wed, 1 Apr 2020 17:13:21 -0400 Subject: [PATCH 043/603] added Rewarded Video section (#1200) also edited all examples so they include the full openRTB context --- docs/endpoints/openrtb2/auction.md | 179 ++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 54 deletions(-) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index 0f03960190d..bd421850d1f 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -95,8 +95,14 @@ If you find that some bidders use Gross bids, publishers can adjust for it with ``` { - "appnexus: 0.8, - "rubicon": 0.7 + "ext": { + "prebid": { + "bidadjustmentfactors": { + "appnexus: 0.8, + "rubicon": 0.7 + } + } + } } ``` @@ -114,17 +120,21 @@ to set these params on the response at `response.seatbid[i].bid[j].ext.prebid.ta ``` { - "pricegranularity": { - "precision": 2, - "ranges": [ - { + "ext": { + "prebid": { + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [{ "max":20.00, "increment":0.10 // This is equivalent to the deprecated "pricegranularity": "medium" - } - ] - }, - "includewinners": false // Optional param defaulting to true - "includebidderkeys": false // Optional param defaulting to true + }] + }, + "includewinners": false, // Optional param defaulting to true + "includebidderkeys": false // Optional param defaulting to true + } + } + } } ``` The list of price granularity ranges must be given in order of increasing `max` values. If `precision` is omitted, it will default to `2`. The minimum of a range will be 0 or the previous `max`. Any cmp above the largest `max` will go in the `max` pricebucket. @@ -159,9 +169,20 @@ MediaType PriceGranularity (PBS-Java only) - when a single OpenRTB request conta ``` { - "hb_bidder_{bidderName}": "The seatbid.seat which contains this bid", - "hb_size_{bidderName}": "A string like '300x250' using bid.w and bid.h for this bid", - "hb_pb_{bidderName}": "The bid.cpm, rounded down based on the price granularity." + "seatbid": [{ + "bid": [{ + ... + "ext": { + "prebid": { + "targeting": { + "hb_bidder_{bidderName}": "The seatbid.seat which contains this bid", + "hb_size_{bidderName}": "A string like '300x250' using bid.w and bid.h for this bid", + "hb_pb_{bidderName}": "The bid.cpm, rounded down based on the price granularity." + } + } + } + }] + }] } ``` @@ -183,8 +204,16 @@ In most cases, this is probably a bad idea. ``` { - "appnexus": "some-appnexus-id", - "rubicon": "some-rubicon-id" + "user": { + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "some-appnexus-id", + "rubicon": "some-rubicon-id" + } + } + } + } } ``` @@ -245,11 +274,9 @@ This prevents breaking API changes as new Bidders are added to the project. For example, if the Request defines an alias like this: ``` -{ "aliases": { "appnexus": "rubicon" } -} ``` then any `imp.ext.appnexus` params will actually go to the **rubicon** adapter. @@ -273,19 +300,17 @@ For example, a request may return this in `response.ext` ``` { - "errors": { - "appnexus": [ - { - "code": 2, - "message": "A hybrid Banner/Audio Imp was offered, but Appnexus doesn't support Audio." - } - ], - "rubicon": [ - { - "code": 1, - "message": "The request exceeded the timeout allocated" - } - ] + "ext": { + "errors": { + "appnexus": [{ + "code": 2, + "message": "A hybrid Banner/Audio Imp was offered, but Appnexus doesn't support Audio." + }], + "rubicon": [{ + "code": 1, + "message": "The request exceeded the timeout allocated" + }] + } } } ``` @@ -319,7 +344,15 @@ A typical `storedrequest` value looks like this: ``` { - "id": "some-id" + "imp": [{ + "ext": { + "prebid": { + "storedrequest": { + "id": "some-id" + } + } + } + }] } ``` @@ -331,12 +364,18 @@ Bids can be temporarily cached on the server by sending the following data as `r ``` { - "bids": {}, - "vastxml": {} + "ext": { + "prebid": { + "cache": { + "bids": {}, + "vastxml": {} + } + } + } } ``` -Both `bids` and `vastxml` are optional, but one of the two is required. This property will have no effect +Both `bids` and `vastxml` are optional, but one of the two is required if you want to cache bids. This property will have no effect unless `request.ext.prebid.targeting` is also set in the request. If `bids` is present, Prebid Server will make a _best effort_ to include these extra @@ -403,8 +442,35 @@ Example: PBS receiving a request for an interstitial imp and these parameters set, it will rewrite the format object within the interstitial imp. If the format array's first object is a size, PBS will take it as the max size for the interstitial. If that size is 1x1, it will look up the device's size and use that as the max size. If the format is not present, it will also use the device size as the max size. (1x1 support so that you don't have to omit the format object to use the device size) PBS with interstitial support will come preconfigured with a list of common ad sizes. Preferentially organized by weighing the larger and more common sizes first. But no guarantees to the ordering will be made. PBS will generate a new format list for the interstitial imp by traversing this list and picking the first 10 sizes that fall within the imp's max size and minimum percentage size. There will be no attempt to favor aspect ratios closer to the original size's aspect ratio. The limit of 10 is enforced to ensure we don't overload bidders with an overlong list. All the interstitial parameters will still be passed to the bidders, so they may recognize them and use their own size matching algorithms if they prefer. +#### Currency Support + +To set the desired 'ad server currency', use the standard OpenRTB `cur` attribute. Note that Prebid Server only looks at the first currency in the array. + +``` + "cur": ["USD"] +``` + +If you want or need to define currency conversion rates (e.g. for currencies that your Prebid Server doesn't support), +define ext.prebid.currency.rates. (Currently supported in PBS-Java only) + +``` +"ext": { + "prebid": { + "currency": { + "rates": { + "USD": { "UAH": 24.47, "ETB": 32.04 } + } + } + } +} +``` + +If it exists, a rate defined in ext.prebid.currency.rates has the highest priority. +If a currency rate doesn't exist in the request, the external file will be used. + #### Supply Chain Support + Basic supply chains are passed to Prebid Server on `source.ext.schain` and passed through to bid adapters. Prebid Server does not currently offer the ability to add a node to the supply chain. Bidder-specific schains (PBS-Java only): @@ -419,6 +485,11 @@ In this scenario, Prebid Server sends the first schain object to `bidderA` and t If there's already an source.ext.schain and a bidder is named in ext.prebid.schains (or covered by the wildcard condition), ext.prebid.schains takes precedent. +#### Rewarded Video (PBS-Java only) + +Rewarded video is a way to incentivize users to watch ads by giving them 'points' for viewing an ad. A Prebid Server +client can declare a given adunit as eligible for rewards by declaring `imp.ext.prebid.is_rewarded_inventory:1`. + #### Stored Responses (PBS-Java only) While testing SDK and video integrations, it's important, but often difficult, to get consistent responses back from bidders that cover a range of scenarios like different CPM values, deals, etc. Prebid Server supports a debugging workflow in two ways: @@ -583,33 +654,33 @@ It specifies where in the OpenRTB request non-standard attributes should be pass ``` { - ext: { - prebid: { - data: { bidders: [ 'rubicon', 'appnexus' ] } // these are the bidders allowed to see protected data + "ext": { + "prebid": { + "data": { "bidders": [ "rubicon", "appnexus" ] } // these are the bidders allowed to see protected data } }, - site: { - keywords: "", - search: "", - ext: { + "site": { + "keywords": "", + "search": "", + "ext": { data: { GLOBAL CONTEXT DATA } // only seen by bidders named in ext.prebid.data.bidders[] } }, - user: { - keywords: "", - gender: "", - yob: 1999, - geo: {}, - ext: { + "user": { + "keywords": "", + "gender": "", + "yob": 1999, + "geo": {}, + "ext": { data: { GLOBAL USER DATA } // only seen by bidders named in ext.prebid.data.bidders[] } }, - imp: [ - ext: { - context: { - keywords: "", - search: "", - data: { ADUNIT SPECFIC CONTEXT DATA } // can be seen by all bidders + "imp": [ + "ext": { + "context": { + "keywords": "", + "search": "", + "data": { ADUNIT SPECFIC CONTEXT DATA } // can be seen by all bidders } } ] From 3665275ff778b6a95fe583053dedf4c0a1365529 Mon Sep 17 00:00:00 2001 From: Rade Popovic <32302052+nanointeractive@users.noreply.github.com> Date: Thu, 2 Apr 2020 23:40:42 +0200 Subject: [PATCH 044/603] nanointeractive adapter (#1213) * nanointeractive adapter * nanointeractive adapter, changes after review * nanointeractive adapter * nanointeractive adapter, changes after review * formatting --- adapters/nanointeractive/nanointeractive.go | 172 ++++++++++++++++++ .../nanointeractive/nanointeractive_test.go | 10 + .../exemplary/simple-banner.json | 90 +++++++++ .../params/race/banner.json | 3 + .../supplemental/bad_response.json | 63 +++++++ .../supplemental/invalid-params.json | 81 +++++++++ .../supplemental/multi-param.json | 151 +++++++++++++++ .../supplemental/status_204.json | 58 ++++++ .../supplemental/status_400.json | 63 +++++++ .../supplemental/status_418.json | 63 +++++++ adapters/nanointeractive/params_test.go | 63 +++++++ adapters/nanointeractive/usersync.go | 12 ++ adapters/nanointeractive/usersync_test.go | 36 ++++ config/config.go | 2 + exchange/adapter_map.go | 32 ++-- openrtb_ext/bidders.go | 2 + openrtb_ext/imp_nanointeractive.go | 10 + static/bidder-info/nanointeractive.yaml | 9 + static/bidder-params/nanointeractive.json | 32 ++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 21 files changed, 940 insertions(+), 15 deletions(-) create mode 100644 adapters/nanointeractive/nanointeractive.go create mode 100644 adapters/nanointeractive/nanointeractive_test.go create mode 100644 adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json create mode 100644 adapters/nanointeractive/nanointeractivetest/params/race/banner.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json create mode 100644 adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json create mode 100644 adapters/nanointeractive/params_test.go create mode 100644 adapters/nanointeractive/usersync.go create mode 100644 adapters/nanointeractive/usersync_test.go create mode 100644 openrtb_ext/imp_nanointeractive.go create mode 100644 static/bidder-info/nanointeractive.yaml create mode 100644 static/bidder-params/nanointeractive.json diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go new file mode 100644 index 00000000000..72832893af1 --- /dev/null +++ b/adapters/nanointeractive/nanointeractive.go @@ -0,0 +1,172 @@ +package nanointeractive + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type NanoInteractiveAdapter struct { + endpoint string +} + +func (a *NanoInteractiveAdapter) Name() string { + return "Nano" +} + +func (a *NanoInteractiveAdapter) SkipNoCookies() bool { + return false +} + +func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + var errs []error + var validImps []openrtb.Imp + + var adapterRequests []*adapters.RequestData + var referer string = "" + + for i := 0; i < len(bidRequest.Imp); i++ { + + ref, err := checkImp(&bidRequest.Imp[i]) + + // If the parsing is failed, remove imp and add the error. + if err != nil { + errs = append(errs, err) + continue + } + if referer == "" && ref != "" { + referer = ref + } + validImps = append(validImps, bidRequest.Imp[i]) + } + + if len(validImps) == 0 { + errs = append(errs, fmt.Errorf("no impressions in the bid request")) + return nil, errs + } + + // set referer origin + if referer != "" { + if bidRequest.Site == nil { + bidRequest.Site = &openrtb.Site{} + } + bidRequest.Site.Ref = referer + } + + bidRequest.Imp = validImps + + reqJSON, err := json.Marshal(bidRequest) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + if bidRequest.Device != nil { + headers.Add("User-Agent", bidRequest.Device.UA) + headers.Add("X-Forwarded-For", bidRequest.Device.IP) + } + if bidRequest.Site != nil { + headers.Add("Referer", bidRequest.Site.Page) + } + + // set user's cookie + if bidRequest.User != nil && bidRequest.User.BuyerUID != "" { + headers.Add("Cookie", "Nano="+bidRequest.User.BuyerUID) + } + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }) + + return adapterRequests, errs +} + +func (a *NanoInteractiveAdapter) MakeBids( + internalRequest *openrtb.BidRequest, + externalRequest *adapters.RequestData, + response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } else if response.StatusCode == http.StatusBadRequest { + return nil, []error{adapters.BadInput("Invalid request.")} + } else if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("unexpected HTTP status %d.", response.StatusCode), + }} + } + + var openRtbBidResponse openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("bad server body response"), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRtbBidResponse.SeatBid[0].Bid)) + bidResponse.Currency = openRtbBidResponse.Cur + + sb := openRtbBidResponse.SeatBid[0] + for i := 0; i < len(sb.Bid); i++ { + if !(sb.Bid[i].Price > 0) { + continue + } + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + return bidResponse, nil +} + +func checkImp(imp *openrtb.Imp) (string, error) { + // We support only banner impression + if imp.Banner == nil { + return "", fmt.Errorf("invalid MediaType. NanoInteractive only supports Banner type. ImpID=%s", imp.ID) + } + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return "", fmt.Errorf("ext not provided; ImpID=%s", imp.ID) + } + + var nanoExt openrtb_ext.ExtImpNanoInteractive + if err := json.Unmarshal(bidderExt.Bidder, &nanoExt); err != nil { + return "", fmt.Errorf("ext.bidder not provided; ImpID=%s", imp.ID) + } + if nanoExt.Pid == "" { + return "", fmt.Errorf("pid is empty; ImpID=%s", imp.ID) + } + + if nanoExt.Ref != "" { + return string(nanoExt.Ref), nil + } + + return "", nil +} + +func NewNanoIneractiveBidder(endpoint string) *NanoInteractiveAdapter { + return &NanoInteractiveAdapter{ + endpoint: endpoint, + } +} + +func NewNanoInteractiveAdapter(uri string) *NanoInteractiveAdapter { + return &NanoInteractiveAdapter{ + endpoint: uri, + } +} diff --git a/adapters/nanointeractive/nanointeractive_test.go b/adapters/nanointeractive/nanointeractive_test.go new file mode 100644 index 00000000000..fa7069a5da3 --- /dev/null +++ b/adapters/nanointeractive/nanointeractive_test.go @@ -0,0 +1,10 @@ +package nanointeractive + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "nanointeractivetest", NewNanoIneractiveBidder("https://ad.audiencemanager.de/hbs")) +} diff --git a/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json b/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..20cc70b6785 --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300,"h": 250} + ] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "nanointeractive", + "bid": [{ + "id": "1", + "impid": "test-imp-id", + "price": 0.4580126, + "adm": "", + "adid": "test_ad_id", + "adomain": ["audiencemanager.de"], + "cid": "test_cid", + "crid": "test_banner_crid", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5a7789eg2662b524d8d7264a96", + "cur": "EUR" + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.4580126, + "adm": "", + "adid": "test_ad_id", + "adomain": ["yahoo.com"], + "cid": "test_cid", + "crid": "test_banner_crid", + "h": 250, + "w": 300 + }, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/params/race/banner.json b/adapters/nanointeractive/nanointeractivetest/params/race/banner.json new file mode 100644 index 00000000000..bb35ea8488a --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "pid": "58bfec94eb0a1916fa380163" +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json b/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json new file mode 100644 index 00000000000..587c952a042 --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "213" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "213" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "bad server body response", + "comparison": "literal" + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json b/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json new file mode 100644 index 00000000000..631dc99e5a8 --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": {}, + "ext": { + "bidder": {} + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [{"w": 300, "h": 250}] + } + }, + { + "id": "test-imp-id-4", + "video": {}, + "ext": { + "bidder": {} + } + }, + { + "id": "test-imp-id-5", + "audio": { + "startdelay": 0, + "api": [] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + }, + "device": { + "os": "android" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "pid is empty; ImpID=test-imp-id-1", + "comparison": "literal" + }, + { + "value": "ext.bidder not provided; ImpID=test-imp-id-2", + "comparison": "literal" + }, + { + "value": "ext not provided; ImpID=test-imp-id-3", + "comparison": "literal" + }, + { + "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-4", + "comparison": "literal" + }, + { + "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-5", + "comparison": "literal" + }, + { + "value": "no impressions in the bid request", + "comparison": "literal" + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json b/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json new file mode 100644 index 00000000000..27e7bec1f5f --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163", + "ref": "https://nanointeractive.com" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163", + "nq": ["search query"], + "category": "Automotive", + "subId": "a23", + "ref": "https://nanointeractive.com" + } + } + } + ], + "device": { + "ip": "127.0.0.1", + "ua": "user_agent" + }, + "user": { + "buyeruid": "userId" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300,"h": 250} + ] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163", + "ref": "https://nanointeractive.com" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [{ "w": 300,"h": 250} + ] + }, + "ext": { + "bidder": { + "pid": "58bfec94eb0a1916fa380163", + "nq": ["search query"], + "category": "Automotive", + "subId": "a23", + "ref": "https://nanointeractive.com" + } + } + } + ], + "site": { + "ref": "https://nanointeractive.com" + }, + "device": { + "ip": "127.0.0.1", + "ua": "user_agent" + }, + "user": { + "buyeruid": "userId" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "nanointeractive", + "bid": [{ + "id": "1", + "impid": "test-imp-id", + "price": 0.4580126, + "adm": "", + "adid": "test_ad_id", + "adomain": ["audiencemanager.de"], + "cid": "test_cid", + "crid": "test_banner_crid", + "h": 250, + "w": 300 + },{ + "id": "2", + "impid": "test-imp-id2", + "price": 0, + "adm": "", + "adid": "test_ad_id", + "adomain": ["audiencemanager.de"], + "cid": "test_cid", + "crid": "test_banner_crid", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5a7789eg2662b524d8d7264a96", + "cur": "EUR" + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.4580126, + "adm": "", + "adid": "test_ad_id", + "adomain": ["yahoo.com"], + "cid": "test_cid", + "crid": "test_banner_crid", + "h": 250, + "w": 300 + }, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json new file mode 100644 index 00000000000..ed4d8ff38b8 --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json new file mode 100644 index 00000000000..f02bd478656 --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Invalid request.", + "comparison": "literal" + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json b/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json new file mode 100644 index 00000000000..b7ed65da2af --- /dev/null +++ b/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ad.audiencemanager.de/hbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "123" + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "unexpected HTTP status 418.", + "comparison": "literal" + } + ] +} diff --git a/adapters/nanointeractive/params_test.go b/adapters/nanointeractive/params_test.go new file mode 100644 index 00000000000..b290f3d94b1 --- /dev/null +++ b/adapters/nanointeractive/params_test.go @@ -0,0 +1,63 @@ +package nanointeractive + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/nanointeractive.json +// +// These also validate the format of the external API: request.imp[i].ext.nanointeracive + +// TestValidParams makes sure that the NanoInteractive schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected NanoInteractive params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Marsmedia schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pid": "dafad098"}`, + `{"pid":"dfasfda","nq":["search query"]}`, + `{"pid":"dfasfda","nq":["search query"],"subId":"any string value","category":"any string value"}`, +} + +var invalidParams = []string{ + `{"pid":123}`, + `{"pid":"12323","nq":"search query not an array"}`, + `{"pid":"12323","category":1}`, + `{"pid":"12323","subId":23}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, + `placementId`, + `zone`, + `zoneId`, +} diff --git a/adapters/nanointeractive/usersync.go b/adapters/nanointeractive/usersync.go new file mode 100644 index 00000000000..e6227436fb2 --- /dev/null +++ b/adapters/nanointeractive/usersync.go @@ -0,0 +1,12 @@ +package nanointeractive + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewNanoInteractiveSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("nanointeractive", 72, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/nanointeractive/usersync_test.go b/adapters/nanointeractive/usersync_test.go new file mode 100644 index 00000000000..ec9787bc20d --- /dev/null +++ b/adapters/nanointeractive/usersync_test.go @@ -0,0 +1,36 @@ +package nanointeractive + +import ( + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestNewNanoInteractiveSyncer(t *testing.T) { + syncURL := "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + userSync := NewNanoInteractiveSyncer(syncURLTemplate) + syncInfo, err := userSync.GetUsersyncInfo( + privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + }, + CCPA: ccpa.Policy{ + Value: "1NYN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr=1&consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 72, userSync.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index d4edab2b53f..999b1870b54 100644 --- a/config/config.go +++ b/config/config.go @@ -521,6 +521,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") @@ -713,6 +714,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") + v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 05f44e24b66..f7b970c571b 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -39,6 +39,7 @@ import ( "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" + "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" @@ -96,21 +97,22 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppSecret), - openrtb_ext.BidderGamma: gamma.NewGammaBidder(cfg.Adapters[string(openrtb_ext.BidderGamma)].Endpoint), - openrtb_ext.BidderGamoshi: gamoshi.NewGamoshiBidder(cfg.Adapters[string(openrtb_ext.BidderGamoshi)].Endpoint), - openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), - openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), - openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), - openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), - openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), - openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), - openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), - openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), - openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), - openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), - openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), - openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), - openrtb_ext.BidderRTBHouse: rtbhouse.NewRTBHouseBidder(cfg.Adapters[string(openrtb_ext.BidderRTBHouse)].Endpoint), + openrtb_ext.BidderGamma: gamma.NewGammaBidder(cfg.Adapters[string(openrtb_ext.BidderGamma)].Endpoint), + openrtb_ext.BidderGamoshi: gamoshi.NewGamoshiBidder(cfg.Adapters[string(openrtb_ext.BidderGamoshi)].Endpoint), + openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), + openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), + openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), + openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), + openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), + openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), + openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), + openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), + openrtb_ext.BidderNanoInteractive: nanointeractive.NewNanoIneractiveBidder(cfg.Adapters[string(openrtb_ext.BidderNanoInteractive)].Endpoint), + openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), + openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), + openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), + openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), + openrtb_ext.BidderRTBHouse: rtbhouse.NewRTBHouseBidder(cfg.Adapters[string(openrtb_ext.BidderRTBHouse)].Endpoint), openrtb_ext.BidderRubicon: rubicon.NewRubiconBidder( client, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e3f186db333..ec9745563ef 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -57,6 +57,7 @@ const ( BidderLockerDome BidderName = "lockerdome" BidderMarsmedia BidderName = "marsmedia" BidderMgid BidderName = "mgid" + BidderNanoInteractive BidderName = "nanointeractive" BidderOpenx BidderName = "openx" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" @@ -119,6 +120,7 @@ var BidderMap = map[string]BidderName{ "lockerdome": BidderLockerDome, "marsmedia": BidderMarsmedia, "mgid": BidderMgid, + "nanointeractive": BidderNanoInteractive, "openx": BidderOpenx, "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go new file mode 100644 index 00000000000..28db5be0d07 --- /dev/null +++ b/openrtb_ext/imp_nanointeractive.go @@ -0,0 +1,10 @@ +package openrtb_ext + +// ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive +type ExtImpNanoInteractive struct { + Pid string `json:"pid"` + Nq []string `json:"nq, omitempty"` + Category string `json:"category, omitempty"` + SubId string `json:"subId, omitempty"` + Ref string `json:"ref, omitempty"` +} diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml new file mode 100644 index 00000000000..244e7602950 --- /dev/null +++ b/static/bidder-info/nanointeractive.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "development@nanointeractive.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-params/nanointeractive.json b/static/bidder-params/nanointeractive.json new file mode 100644 index 00000000000..707dff2fa50 --- /dev/null +++ b/static/bidder-params/nanointeractive.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "NanoInteractive Adapter Params", + "description": "A schema which validates params accepted by the NanoInteractive adapter", + "type": "object", + "properties": { + "pid": { + "type": "string", + "description": "Placement idd" + }, + "nq": { + "type": "array", + "items": { + "type": "string" + }, + "description": "search queries" + }, + "category": { + "type": "string", + "description": "IAB Category" + }, + "subId": { + "type": "string", + "description": "any segment value provided by publisher" + }, + "ref" : { + "type": "string", + "description": "referer" + } + }, + "required": ["pid"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index c7ad70b7eff..be0392f2dbb 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -34,6 +34,7 @@ import ( "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" + "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -96,6 +97,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 3de64ec1eb0..383e24d82cf 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -43,6 +43,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, + string(openrtb_ext.BidderNanoInteractive): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig, From fb386190f4491648bb1e8d1b0345a333be1c0393 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 6 Apr 2020 18:53:03 -0400 Subject: [PATCH 045/603] Typos Fix (#1236) * Fix Typo * Fixed More Typos --- adapters/adapterstest/test_json.go | 2 +- analytics/config/config_test.go | 6 +++--- config/config.go | 2 +- config/config_test.go | 4 ++-- config/stored_requests.go | 2 +- docs/bidders/appnexus.md | 2 +- docs/bidders/audienceNetwork.md | 2 +- docs/bidders/sovrn.md | 2 +- docs/developers/automated-tests.md | 2 +- docs/developers/cookie-syncs.md | 2 +- docs/developers/default-request.md | 6 +++--- docs/endpoints/openrtb2/amp.md | 2 +- docs/endpoints/openrtb2/auction.md | 8 +++---- endpoints/openrtb2/amp_auction_test.go | 10 ++++----- endpoints/openrtb2/auction_test.go | 10 ++++----- endpoints/openrtb2/video_auction_test.go | 6 +++--- exchange/bidder.go | 2 +- exchange/exchange_test.go | 2 +- gdpr/gdpr.go | 6 +++--- main.go | 4 +++- openrtb_ext/bid.go | 2 +- openrtb_ext/request.go | 4 ++-- openrtb_ext/request_test.go | 8 +++---- pbsmetrics/metrics.go | 2 +- pbsmetrics/prometheus/prometheus.go | 4 ++-- pbsmetrics/prometheus/prometheus_test.go | 6 +++--- .../aspects/request_timeout_handler_test.go | 21 ++++++++++--------- ssl/ssl_test.go | 2 +- .../backends/db_fetcher/fetcher.go | 2 +- stored_requests/events/events_test.go | 2 +- stored_requests/events/http/http.go | 2 +- stored_requests/fetcher.go | 2 +- 32 files changed, 71 insertions(+), 68 deletions(-) diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index a0d1954894a..7602ab16e41 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -301,7 +301,7 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte) if diff.Modified() { var left interface{} if err := json.Unmarshal(actual, &left); err != nil { - t.Fatalf("%s json did not match, but unmarhsalling failed. %v", description, err) + t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err) } printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{ ShowArrayIndex: true, diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 0fd3ec2019e..7d97fb5f1be 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -22,7 +22,7 @@ func TestSampleModule(t *testing.T) { Response: &openrtb.BidResponse{}, }) if count != 1 { - t.Errorf("PBSAnalyticsModule failed at LogAuctionObejct") + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") } am.LogSetUIDObject(&analytics.SetUIDObject{ @@ -33,12 +33,12 @@ func TestSampleModule(t *testing.T) { Success: true, }) if count != 2 { - t.Errorf("PBSAnalyticsModule failed at LogSetUIDObejct") + t.Errorf("PBSAnalyticsModule failed at LogSetUIDObject") } am.LogCookieSyncObject(&analytics.CookieSyncObject{}) if count != 3 { - t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObejct") + t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject") } am.LogAmpObject(&analytics.AmpObject{}) diff --git a/config/config.go b/config/config.go index 999b1870b54..2cb5f8f2e66 100644 --- a/config/config.go +++ b/config/config.go @@ -221,7 +221,7 @@ const ( type Adapter struct { Endpoint string `mapstructure:"endpoint"` // Required // UserSyncURL is the URL returned by /cookie_sync for this Bidder. It is _usually_ optional. - // If not defined, sensible defaults will be derved based on the config.external_url. + // If not defined, sensible defaults will be derived based on the config.external_url. // Note that some Bidders don't have sensible defaults, because their APIs require an ID that will vary // from one PBS host to another. // diff --git a/config/config_test.go b/config/config_test.go index 78630e071d9..9677ce2aaba 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -417,9 +417,9 @@ func TestCookieSizeError(t *testing.T) { } for i := range testCases { if testCases[i].expectError { - assert.Error(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCooki.MaxCookieSizeBytes less than MIN_COOKIE_SIZE_BYTES = %d and not equal to zero should return an error", MIN_COOKIE_SIZE_BYTES)) + assert.Error(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes less than MIN_COOKIE_SIZE_BYTES = %d and not equal to zero should return an error", MIN_COOKIE_SIZE_BYTES)) } else { - assert.NoError(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCooki.MaxCookieSizeBytes greater than MIN_COOKIE_SIZE_BYTES = %d or equal to zero should not return an error", MIN_COOKIE_SIZE_BYTES)) + assert.NoError(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes greater than MIN_COOKIE_SIZE_BYTES = %d or equal to zero should not return an error", MIN_COOKIE_SIZE_BYTES)) } } } diff --git a/config/stored_requests.go b/config/stored_requests.go index 0d9e773205e..04e400f9b7c 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -402,7 +402,7 @@ func (cfg *PostgresUpdatePolling) validate(errs configErrors) configErrors { return errs } -// MakeQuery builds a query which can fetch numReqs Stored Requetss and numImps Stored Imps. +// MakeQuery builds a query which can fetch numReqs Stored Requests and numImps Stored Imps. // See the docs on PostgresConfig.QueryTemplate for a description of how it works. func (cfg *PostgresFetcherQueriesSlim) MakeQuery(numReqs int, numImps int) (query string) { return resolve(cfg.QueryTemplate, numReqs, numImps) diff --git a/docs/bidders/appnexus.md b/docs/bidders/appnexus.md index 8b706adc122..e4032313f25 100644 --- a/docs/bidders/appnexus.md +++ b/docs/bidders/appnexus.md @@ -15,7 +15,7 @@ The AppNexus endpoint expects `imp.displaymanagerver` to be populated for mobile requests, however not all SDKs will populate this field. If the `imp.displaymanagerver` field is not supplied for an `imp`, but `request.app.ext.prebid.source` and `request.app.ext.prebid.version` are supplied, the adapter will fill in a value for -`diplaymanagerver`. It will concatonate the two `app` fields as `-` fo fill in +`diplaymanagerver`. It will concatenate the two `app` fields as `-` fo fill in the empty `displaymanagerver` before sending the request to AppNexus. ## Test Request diff --git a/docs/bidders/audienceNetwork.md b/docs/bidders/audienceNetwork.md index 04357d616b1..d55e8218a81 100644 --- a/docs/bidders/audienceNetwork.md +++ b/docs/bidders/audienceNetwork.md @@ -3,6 +3,6 @@ ## Mobile Bids Audience Network will not bid on requests made from device simulators. -When testingfor Mobile bids, you must make bid requests using a real device. +When testing for Mobile bids, you must make bid requests using a real device. **Note:** Audience Network is disabled by default. Please enable it in the app config if you wish to use it. Make sure you provide the partnerID for the auctions to run correctly. \ No newline at end of file diff --git a/docs/bidders/sovrn.md b/docs/bidders/sovrn.md index 544cb8a6764..bc6d42333e8 100644 --- a/docs/bidders/sovrn.md +++ b/docs/bidders/sovrn.md @@ -1,3 +1,3 @@ Sovrn supports 2 parameters to be present in the `ext` object of impressions sent to it: - tagid: a string containing the sovrn-specific id(s) for the publisher's ad tag(s) they would like to bid with. This is a required field -- bidfloor: The minimium acceptable bid, in CPM, using US Dollars. This is an optional field. \ No newline at end of file +- bidfloor: The minimum acceptable bid, in CPM, using US Dollars. This is an optional field. \ No newline at end of file diff --git a/docs/developers/automated-tests.md b/docs/developers/automated-tests.md index 12532237e08..0dff9b04212 100644 --- a/docs/developers/automated-tests.md +++ b/docs/developers/automated-tests.md @@ -9,7 +9,7 @@ To reproduce these tests locally, use: ## Writing Tests -Tests for `some-file.go` should be placed in the file `some-file_test.go` in the same paackage. +Tests for `some-file.go` should be placed in the file `some-file_test.go` in the same package. For more info on how to write tests in Go, see [the Go docs](https://golang.org/pkg/testing/). ## Adapter Tests diff --git a/docs/developers/cookie-syncs.md b/docs/developers/cookie-syncs.md index 36c6b85b636..75a3e3b0ef8 100644 --- a/docs/developers/cookie-syncs.md +++ b/docs/developers/cookie-syncs.md @@ -1,6 +1,6 @@ # Cookie Sync Technical Details -This document describes the mechancis of a Prebid Server cookie sync. +This document describes the mechanics of a Prebid Server cookie sync. ## Motivation diff --git a/docs/developers/default-request.md b/docs/developers/default-request.md index 2337ccd8da0..f071d91bad6 100644 --- a/docs/developers/default-request.md +++ b/docs/developers/default-request.md @@ -1,6 +1,6 @@ # Server Based Global Default Request -This allows a defaut stored request to be defined that allows the server to set up some defaults for all incoming requests. A request specified stored request will override these defaults, and of course any options specified directly in the stored request override both. The default stored request is only read on server startup, it is meant as an installation static default rather than a dynamic tuning option. +This allows a default stored request to be defined that allows the server to set up some defaults for all incoming requests. A request specified stored request will override these defaults, and of course any options specified directly in the stored request override both. The default stored request is only read on server startup, it is meant as an installation static default rather than a dynamic tuning option. A common use case is to "hard code" aliases into the server. This saves having to specify them on all incoming requests, and/or on all stored requests. To help support automation and alias discovery we can flag that any aliases found in the file be added to the bidder info endpoints. @@ -35,8 +35,8 @@ The `filename` option is the path/filename of a JSON file containing the default ``` This will be JSON merged into the incoming requests at the top level. These will be used as fallbacks which can be overridden by both Stored Requests _and_ the incoming HTTP request payload. -The `info` option determines if the alised bidders will be exposed on the `/info` endpoints. If true the alias name will be added to the list returned by -`/info/bidders` and the info JSON for the core bidder will be coppied into `/info/bidder/{biddername}` with the addition of the field +The `info` option determines if the aliased bidders will be exposed on the `/info` endpoints. If true the alias name will be added to the list returned by +`/info/bidders` and the info JSON for the core bidder will be copied into `/info/bidder/{biddername}` with the addition of the field `"alias_of": "{coreBidder}"` to indicate that it is an aliases, and of which core bidder. Turning the info support on may be useful for hosts that want to support automation around the `/info` endpoints that will include the predefined aliases. This config option may be deprecated in a future version to promote a consistency in the endpoint functionality, depending on the perceived need for the option. diff --git a/docs/endpoints/openrtb2/amp.md b/docs/endpoints/openrtb2/amp.md index b792ae6ec5d..16fa451ef36 100644 --- a/docs/endpoints/openrtb2/amp.md +++ b/docs/endpoints/openrtb2/amp.md @@ -100,7 +100,7 @@ This endpoint supports the following query parameters: 6. `curl` - the canonical URL of the page 7. `timeout` - the publisher-specified timeout for the RTC callout - A configuration option `amp_timeout_adjustment_ms` may be set to account for estimated latency so that Prebid Server can handle timeouts from adapters and respond to the AMP RTC request before it times out. -8. `debug` - When set to `1`, the respones will contain extra info for debugging. +8. `debug` - When set to `1`, the response will contain extra info for debugging. For information on how these get from AMP into this endpoint, see [this pull request adding the query params to the Prebid callout](https://github.com/ampproject/amphtml/pull/14155) and [this issue adding support for network-level RTC macros](https://github.com/ampproject/amphtml/issues/12374). diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index bd421850d1f..67430e51481 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -228,7 +228,7 @@ for each Bidder by using the `/cookie_sync` endpoint, and calling the URLs that #### Native Request -For each native request, the `assets` objects's `id` field must not be defined. Prebid Server will set this automatically, using the index of the asset in the array as the ID. +For each native request, the `assets` object's `id` field must not be defined. Prebid Server will set this automatically, using the index of the asset in the array as the ID. #### Bidder Aliases @@ -265,7 +265,7 @@ This can be used to request bids from the same Bidder with different params. For ``` For all intents and purposes, the alias will be treated as another Bidder. This new Bidder will behave exactly -like the original, except that the Response will contain seprate SeatBids, and any Targeting keys +like the original, except that the Response will contain separate SeatBids, and any Targeting keys will be formed using the alias' name. If an alias overlaps with a core Bidder's name, then the alias will take precedence. @@ -280,7 +280,7 @@ For example, if the Request defines an alias like this: ``` then any `imp.ext.appnexus` params will actually go to the **rubicon** adapter. -It will become impossible to fetch bids from Appnexus within that Request. +It will become impossible to fetch bids from AppNexus within that Request. #### Bidder Response Times @@ -495,7 +495,7 @@ client can declare a given adunit as eligible for rewards by declaring `imp.ext. While testing SDK and video integrations, it's important, but often difficult, to get consistent responses back from bidders that cover a range of scenarios like different CPM values, deals, etc. Prebid Server supports a debugging workflow in two ways: - a stored-auction-response that covers multiple bidder responses -- multiple stored-bid-reponses at the bidder adapter level +- multiple stored-bid-responses at the bidder adapter level **Single Stored Auction Response ID** diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index b25d5b0cc8f..9dc81eb1b9d 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -219,7 +219,7 @@ func TestGDPRConsent(t *testing.T) { responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) - // Parse Resonse + // Parse Response var response AmpResponse if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) @@ -372,7 +372,7 @@ func TestCCPAConsent(t *testing.T) { responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) - // Parse Resonse + // Parse Response var response AmpResponse if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) @@ -431,7 +431,7 @@ func TestNoConsent(t *testing.T) { responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) - // Parse Resonse + // Parse Response var response AmpResponse if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) @@ -478,7 +478,7 @@ func TestInvalidConsent(t *testing.T) { responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) - // Parse Resonse + // Parse Response var response AmpResponse if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) @@ -561,7 +561,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { responseRecorder := httptest.NewRecorder() endpoint(responseRecorder, request, nil) - // Parse Resonse + // Parse Response var response AmpResponse if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 74a70c69415..98dfa66d6a4 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -175,7 +175,7 @@ func TestBadNativeRequests(t *testing.T) { tests.assert(t) } -// TestAliasedRequests makes sure we handle (defuault) aliased bidders properly +// TestAliasedRequests makes sure we handle (default) aliased bidders properly func TestAliasedRequests(t *testing.T) { tests := &getResponseFromDirectory{ dir: "sample-requests/aliased", @@ -289,7 +289,7 @@ func (gr *getResponseFromDirectory) assert(t *testing.T) { filesToAssert = append(filesToAssert, gr.dir+"/"+fileInfo.Name()) } } else { - // Just test the single `gr.file`, and not the entiriety of files that may be found in `gr.dir` + // Just test the single `gr.file`, and not the entirety of files that may be found in `gr.dir` filesToAssert = append(filesToAssert, gr.dir+"/"+gr.file) } @@ -805,7 +805,7 @@ func TestDisabledBidder(t *testing.T) { }, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{"unknownbidder": "The biddder 'unknownbidder' has been disabled."}, + map[string]string{"unknownbidder": "The bidder 'unknownbidder' has been disabled."}, false, []byte{}, openrtb_ext.BidderMap, @@ -839,7 +839,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { &config.Configuration{MaxRequestSize: int64(8096)}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{"unknownbidder": "The biddder 'unknownbidder' has been disabled."}, + map[string]string{"unknownbidder": "The bidder 'unknownbidder' has been disabled."}, false, []byte{}, openrtb_ext.BidderMap, @@ -847,7 +847,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { } errs := deps.validateImpExt(imp, nil, 0) assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext)) - assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The biddder 'unknownbidder' has been disabled."}}, errs) + assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs) } func TestEffectivePubID(t *testing.T) { diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 0199b43f610..d0ce33de1c4 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -44,7 +44,7 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} if err := json.Unmarshal(respBytes, resp); err != nil { - t.Fatalf("Unable to umarshal response.") + t.Fatalf("Unable to unmarshal response.") } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") @@ -197,7 +197,7 @@ func TestVideoEndpointDebugQueryTrue(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} if err := json.Unmarshal(respBytes, resp); err != nil { - t.Fatalf("Unable to umarshal response.") + t.Fatalf("Unable to unmarshal response.") } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") @@ -239,7 +239,7 @@ func TestVideoEndpointDebugQueryFalse(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} if err := json.Unmarshal(respBytes, resp); err != nil { - t.Fatalf("Unable to umarshal response.") + t.Fatalf("Unable to unmarshal response.") } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") diff --git a/exchange/bidder.go b/exchange/bidder.go index 97f64e74bb5..8e95835ffba 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -208,7 +208,7 @@ func addNativeTypes(bid *openrtb.Bid, request *openrtb.BidRequest) (*nativeRespo var errs []error var nativeMarkup *nativeResponse.Response if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { - // Some bidders are returning non-IAB complaiant native markup. In this case Prebid server will not be able to add types. E.g Facebook + // Some bidders are returning non-IAB compliant native markup. In this case Prebid server will not be able to add types. E.g Facebook return nil, errs } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7217e609189..2f115ca4f93 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1762,7 +1762,7 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte) if diff.Modified() { var left interface{} if err := json.Unmarshal(actual, &left); err != nil { - t.Fatalf("%s json did not match, but unmarhsalling failed. %v", description, err) + t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err) } printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{ ShowArrayIndex: true, diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index a6b64203a95..4e36e22fdb9 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -12,17 +12,17 @@ import ( type Permissions interface { // Determines whether or not the host company is allowed to read/write cookies. // - // If the consent string was nonsenical, the returned error will be an ErrorMalformedConsent. + // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. HostCookiesAllowed(ctx context.Context, consent string) (bool, error) // Determines whether or not the given bidder is allowed to user personal info for ad targeting. // - // If the consent string was nonsenical, the returned error will be an ErrorMalformedConsent. + // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) // Determines whether or not to send PI information to a bidder, or mask it out. // - // If the consent string was nonsenical, the returned error will be an ErrorMalformedConsent. + // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) } diff --git a/main.go b/main.go index ae3b7fd5705..d6ba430f059 100644 --- a/main.go +++ b/main.go @@ -42,9 +42,11 @@ func main() { } } +const configFileName = "pbs" + func loadConfig() (*config.Configuration, error) { v := viper.New() - config.SetupViper(v, "pbs") // filke = filename + config.SetupViper(v, configFileName) return config.New(v) } diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 768128c96d6..3b297c7ab5d 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -87,7 +87,7 @@ const ( HbpbConstantKey TargetingKey = "hb_pb" // HbEnvKey exists to support the Prebid Universal Creative. If it exists, the only legal value is mobile-app. - // It will exist only if the incoming bidRequest defiend request.app instead of request.site. + // It will exist only if the incoming bidRequest defined request.app instead of request.site. HbEnvKey TargetingKey = "hb_env" // HbCacheHost and HbCachePath exist to supply cache host and path as targeting parameters diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 9d1456c9618..25b5c881408 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -160,7 +160,7 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { func PriceGranularityFromString(gran string) PriceGranularity { switch gran { case "low": - return priceGranulrityLow + return priceGranularityLow case "med", "medium": // Seems that PBS was written with medium = "med", so hacking that in return priceGranularityMed @@ -175,7 +175,7 @@ func PriceGranularityFromString(gran string) PriceGranularity { return PriceGranularity{} } -var priceGranulrityLow = PriceGranularity{ +var priceGranularityLow = PriceGranularity{ Precision: 2, Ranges: []GranularityRange{{ Min: 0, diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 3291c4f9fb2..e4046a622db 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -8,12 +8,12 @@ import ( "github.com/stretchr/testify/assert" ) -// Test the unmashalling of the prebid extensions and setting default Price Granularity +// Test the unmarshalling of the prebid extensions and setting default Price Granularity func TestExtRequestTargeting(t *testing.T) { extRequest := &ExtRequest{} err := json.Unmarshal([]byte(ext1), extRequest) if err != nil { - t.Errorf("ext1 Unmashall falure: %s", err.Error()) + t.Errorf("ext1 Unmarshall failure: %s", err.Error()) } if extRequest.Prebid.Targeting != nil { t.Error("ext1 Targeting is not nil") @@ -22,7 +22,7 @@ func TestExtRequestTargeting(t *testing.T) { extRequest = &ExtRequest{} err = json.Unmarshal([]byte(ext2), extRequest) if err != nil { - t.Errorf("ext2 Unmashall falure: %s", err.Error()) + t.Errorf("ext2 Unmarshall failure: %s", err.Error()) } if extRequest.Prebid.Targeting == nil { t.Error("ext2 Targeting is nil") @@ -36,7 +36,7 @@ func TestExtRequestTargeting(t *testing.T) { extRequest = &ExtRequest{} err = json.Unmarshal([]byte(ext3), extRequest) if err != nil { - t.Errorf("ext3 Unmashall falure: %s", err.Error()) + t.Errorf("ext3 Unmarshall failure: %s", err.Error()) } if extRequest.Prebid.Targeting == nil { t.Error("ext3 Targeting is nil") diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index aea9735c276..cc836011efa 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -248,7 +248,7 @@ func RequestActions() []RequestAction { // MetricsEngine is a generic interface to record PBS metrics into the desired backend // The first three metrics function fire off once per incoming request, so total metrics -// will equal the total numer of incoming requests. The remaining 5 fire off per outgoing +// will equal the total number of incoming requests. The remaining 5 fire off per outgoing // request to a bidder adapter, so will record a number of hits per incoming request. The // two groups should be consistent within themselves, but comparing numbers between groups // is generally not useful. diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index 7cb80643542..e2b646d5238 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -76,7 +76,7 @@ const ( // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. func NewMetrics(cfg config.PrometheusMetrics) *Metrics { requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} - cacheWriteTimeBuckts := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} + cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} metrics := Metrics{} @@ -112,7 +112,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "prebidcache_write_time_seconds", "Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.", []string{successLabel}, - cacheWriteTimeBuckts) + cacheWriteTimeBuckets) metrics.requests = newCounter(cfg, metrics.Registry, "requests", diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index 4cf9676e1d4..f76480f0852 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -571,7 +571,7 @@ func TestAdapterRequestMetrics(t *testing.T) { var totalCount float64 var totalCookieNoCount float64 var totalCookieYesCount float64 - var totalCookieUnknowmCount float64 + var totalCookieUnknownCount float64 var totalHasBidsCount float64 processMetrics(m.adapterRequests, func(m dto.Metric) { isMetricForAdapter := false @@ -597,7 +597,7 @@ func TestAdapterRequestMetrics(t *testing.T) { case string(pbsmetrics.CookieFlagYes): totalCookieYesCount += value case string(pbsmetrics.CookieFlagUnknown): - totalCookieUnknowmCount += value + totalCookieUnknownCount += value } } } @@ -606,7 +606,7 @@ func TestAdapterRequestMetrics(t *testing.T) { assert.Equal(t, test.expectedCount, totalCount, test.description+":total") assert.Equal(t, test.expectedCookieNoCount, totalCookieNoCount, test.description+":cookie=no") assert.Equal(t, test.expectedCookieYesCount, totalCookieYesCount, test.description+":cookie=yes") - assert.Equal(t, test.expectedCookieUnknownCount, totalCookieUnknowmCount, test.description+":cookie=unknown") + assert.Equal(t, test.expectedCookieUnknownCount, totalCookieUnknownCount, test.description+":cookie=unknown") assert.Equal(t, test.expectedHasBidsCount, totalHasBidsCount, test.description+":hasBids") } } diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go index b6e10fd64bf..5283d5d51e7 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -1,12 +1,13 @@ package aspects import ( - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" "net/http" "net/http/httptest" "testing" + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ func TestAny(t *testing.T) { reqTimeInQueue string reqTimeOut string setHeaders bool - extectedRespCode int + expectedRespCode int expectedRespCodeMessage string expectedRespBody string expectedRespBodyMessage string @@ -28,7 +29,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "6", reqTimeOut: "5", setHeaders: true, - extectedRespCode: http.StatusRequestTimeout, + expectedRespCode: http.StatusRequestTimeout, expectedRespCodeMessage: "Http response code is incorrect, should be 408", expectedRespBody: "Queued request processing time exceeded maximum", expectedRespBodyMessage: "Body should have error message", @@ -38,7 +39,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "0.9", reqTimeOut: "5", setHeaders: true, - extectedRespCode: http.StatusOK, + expectedRespCode: http.StatusOK, expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", @@ -48,7 +49,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "", reqTimeOut: "", setHeaders: false, - extectedRespCode: http.StatusOK, + expectedRespCode: http.StatusOK, expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", @@ -58,7 +59,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "2", reqTimeOut: "", setHeaders: true, - extectedRespCode: http.StatusOK, + expectedRespCode: http.StatusOK, expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", @@ -68,7 +69,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "test1", reqTimeOut: "test2", setHeaders: true, - extectedRespCode: http.StatusInternalServerError, + expectedRespCode: http.StatusInternalServerError, expectedRespCodeMessage: "Http response code is incorrect, should be 400", expectedRespBody: "Request timeout headers are incorrect (wrong format)", expectedRespBodyMessage: "Body should have error message", @@ -78,7 +79,7 @@ func TestAny(t *testing.T) { reqTimeInQueue: "test1", reqTimeOut: "123", setHeaders: true, - extectedRespCode: http.StatusInternalServerError, + expectedRespCode: http.StatusInternalServerError, expectedRespCodeMessage: "Http response code is incorrect, should be 400", expectedRespBody: "Request timeout headers are incorrect (wrong format)", expectedRespBodyMessage: "Body should have error message", @@ -87,7 +88,7 @@ func TestAny(t *testing.T) { for _, test := range testCases { result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders) - assert.Equal(t, test.extectedRespCode, result.Code, test.expectedRespCodeMessage) + assert.Equal(t, test.expectedRespCode, result.Code, test.expectedRespCodeMessage) assert.Equal(t, test.expectedRespBody, string(result.Body.Bytes()), test.expectedRespBodyMessage) } } diff --git a/ssl/ssl_test.go b/ssl/ssl_test.go index c4c29d149ef..b72fb7ae9a3 100644 --- a/ssl/ssl_test.go +++ b/ssl/ssl_test.go @@ -38,7 +38,7 @@ func TestCertsFromFilePoolDontExist(t *testing.T) { // Assert loaded certificates by looking at the length of the subjects array of strings assert.NoError(t, err, "Error thrown by AppendPEMFileToRootCAPool while loading file %s: %v", certificatesFile, err) subjects := certPool.Subjects() - assert.Equal(t, len(subjects), 1, "We only loaded one vertificate from the file, len(subjects) should equal 1") + assert.Equal(t, len(subjects), 1, "We only loaded one certificate from the file, len(subjects) should equal 1") } func TestAppendPEMFileToRootCAPoolFail(t *testing.T) { diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index a8232fd5173..223067c917e 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -113,7 +113,7 @@ func appendErrors(dataType string, ids []string, data map[string]json.RawMessage // // These errors are documented here: https://www.postgresql.org/docs/9.3/static/errcodes-appendix.html func isBadInput(err error) bool { - // Unfortunately, Postgres queries will fail if a non-UUID is passedd into a query for a UUID column. For example: + // Unfortunately, Postgres queries will fail if a non-UUID is passed into a query for a UUID column. For example: // // SELECT uuid, data, dataType FROM stored_requests WHERE uuid IN ('abc'); // diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index aaece692bd2..240a697592a 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -23,7 +23,7 @@ func TestListen(t *testing.T) { TTL: -1, }) - // create channels to syncronize + // create channels to synchronize saveOccurred := make(chan struct{}) invalidateOccurred := make(chan struct{}) listener := NewEventListener( diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index 4f141dac5cd..a6a129eed42 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -142,7 +142,7 @@ func (e *HTTPEvents) refresh(ticker <-chan time.Time) { } } -// proceess unpacks the HTTP response and sends the relevant events to the channels. +// parse unpacks the HTTP response and sends the relevant events to the channels. // It returns true if everything was successful, and false if any errors occurred. func (e *HTTPEvents) parse(endpoint string, resp *httpCore.Response, err error) (*responseContract, bool) { if err != nil { diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 808495e4584..23fdb6b4925 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -30,7 +30,7 @@ type CategoryFetcher interface { FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) } -// AllFetcher is an iterface that encapsulates both the original Fetcher and the CategoryFetcher +// AllFetcher is an interface that encapsulates both the original Fetcher and the CategoryFetcher type AllFetcher interface { FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) From 1af4a6af23d2b808c383f5a193b8809a1b7cb016 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Thu, 9 Apr 2020 07:44:38 -0700 Subject: [PATCH 046/603] Moved hb_pc_cat_dur modification to be before caching (#1250) --- exchange/exchange.go | 20 +++++++++++--------- exchange/exchange_test.go | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 3cab1880456..e625e5ca8f3 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -179,6 +179,12 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque errs = append(errs, errors.New("Unable to marshal response ext for debugging")) } } + + if requestExt.Prebid.SupportDeals { + dealErrs := applyDealSupport(bidRequest, auc, bidCategory) + errs = append(errs, dealErrs...) + } + cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog) if len(cacheErrs) > 0 { errs = append(errs, cacheErrs...) @@ -186,10 +192,6 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque targData.setTargeting(auc, bidRequest.App != nil, bidCategory) } - if requestExt.Prebid.SupportDeals { - dealErrs := applyDealSupport(bidRequest, auc) - errs = append(errs, dealErrs...) - } } // Build the response @@ -210,7 +212,7 @@ type BidderDealTier struct { } // applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded -func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction) []error { +func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory map[string]string) []error { errs := []error{} impDealMap := getDealTiers(bidRequest) @@ -221,7 +223,7 @@ func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction) []error { if topBidPerBidder.dealPriority > 0 { if validateAndNormalizeDealTier(impDeal[bidderString]) { - updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info) + updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info, bidCategory) } else { errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", bidderString, impID)) } @@ -258,16 +260,16 @@ func validateAndNormalizeDealTier(impDeal *DealTier) bool { return len(impDeal.Info.Prefix) > 0 && impDeal.Info.MinDealTier > 0 } -func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo) { +func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo, bidCategory map[string]string) { if bid.dealPriority >= dealTierInfo.MinDealTier { prefixTier := fmt.Sprintf("%s%d_", dealTierInfo.Prefix, bid.dealPriority) - if oldCatDur, ok := bid.bidTargets["hb_pb_cat_dur"]; ok { + if oldCatDur, ok := bidCategory[bid.bid.ID]; ok { oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2) oldCatDurSplit[0] = prefixTier newCatDur := strings.Join(oldCatDurSplit, "") - bid.bidTargets["hb_pb_cat_dur"] = newCatDur + bidCategory[bid.bid.ID] = newCatDur } } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2f115ca4f93..f263eea8569 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1403,7 +1403,10 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + bidCategory := map[string]string{ + bid.bid.ID: test.targ["hb_pb_cat_dur"], + } auc := &auction{ winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ @@ -1413,9 +1416,9 @@ func TestApplyDealSupport(t *testing.T) { }, } - dealErrs := applyDealSupport(bidRequest, auc) + dealErrs := applyDealSupport(bidRequest, auc, bidCategory) - assert.Equal(t, test.expectedHbPbCatDur, auc.winningBidsByBidder["imp_id1"][bidderName].bidTargets["hb_pb_cat_dur"], test.description) + assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName].bid.ID], test.description) if len(test.expectedDealErr) > 0 { assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors") } @@ -1590,11 +1593,14 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority} + bidCategory := map[string]string{ + bid.bid.ID: test.targ["hb_pb_cat_dur"], + } - updateHbPbCatDur(&bid, test.dealTier) + updateHbPbCatDur(&bid, test.dealTier, bidCategory) - assert.Equal(t, test.expectedHbPbCatDur, bid.bidTargets["hb_pb_cat_dur"], test.description) + assert.Equal(t, test.expectedHbPbCatDur, bidCategory[bid.bid.ID], test.description) } } From 733b40d71f38d8b386b3d2bbce02e49476aaa91a Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 13 Apr 2020 11:21:52 -0400 Subject: [PATCH 047/603] replacing info@prebid.org maintainer email addrs (#1256) --- static/bidder-info/appnexus.yaml | 2 +- static/bidder-info/audienceNetwork.yaml | 2 +- static/bidder-info/ix.yaml | 2 +- static/bidder-info/pulsepoint.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml index 585c59b91c6..f1e7ca23cfb 100644 --- a/static/bidder-info/appnexus.yaml +++ b/static/bidder-info/appnexus.yaml @@ -1,5 +1,5 @@ maintainer: - email: "info@prebid.org" + email: "prebid-server@xandr.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml index 34700f2f929..56230bf3f9a 100644 --- a/static/bidder-info/audienceNetwork.yaml +++ b/static/bidder-info/audienceNetwork.yaml @@ -1,5 +1,5 @@ maintainer: - email: "info@prebid.org" + email: "none" capabilities: site: mediaTypes: diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index ff29ec03f77..326989ae9fe 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -1,5 +1,5 @@ maintainer: - email: "info@prebid.org" + email: "pdu-supply-prebid@indexexchange.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index b9fd32427b1..716e453000e 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -1,5 +1,5 @@ maintainer: - email: "info@prebid.org" + email: "ExchangeTeam@pulsepoint.com" capabilities: app: mediaTypes: From 2b334afb690c297f9e72d6d7f466a7fb5fe75518 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 14 Apr 2020 12:26:39 -0400 Subject: [PATCH 048/603] aligning maintainer info (#1258) --- static/bidder-info/33across.yaml | 4 ++-- static/bidder-info/adoppler.yaml | 2 +- static/bidder-info/advangelists.yaml | 2 +- static/bidder-info/brightroll.yaml | 2 +- static/bidder-info/conversant.yaml | 2 +- static/bidder-info/datablocks.yaml | 2 +- static/bidder-info/engagebdr.yaml | 2 +- static/bidder-info/gamoshi.yaml | 2 +- static/bidder-info/gumgum.yaml | 2 +- static/bidder-info/openx.yaml | 2 +- static/bidder-info/rtbhouse.yaml | 2 +- static/bidder-info/sonobi.yaml | 2 +- static/bidder-info/verizonmedia.yaml | 4 ++-- static/bidder-info/visx.yaml | 2 +- static/bidder-info/yieldmo.yaml | 2 +- static/bidder-info/zeroclickfraud.yaml | 2 +- 16 files changed, 18 insertions(+), 18 deletions(-) diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index f0a4447099f..84ba6d68611 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,9 +1,9 @@ maintainer: - email: "dev@33across.com" + email: "headerbidding@33across.com" capabilities: app: mediaTypes: - banner site: mediaTypes: - - banner \ No newline at end of file + - banner diff --git a/static/bidder-info/adoppler.yaml b/static/bidder-info/adoppler.yaml index 7fa79eda163..1b10103923e 100644 --- a/static/bidder-info/adoppler.yaml +++ b/static/bidder-info/adoppler.yaml @@ -1,5 +1,5 @@ maintainer: - email: info@adoppler.com + email: pbs@adoppler.com capabilities: app: mediaTypes: diff --git a/static/bidder-info/advangelists.yaml b/static/bidder-info/advangelists.yaml index e1bc6c0a19b..aed9900d0e7 100644 --- a/static/bidder-info/advangelists.yaml +++ b/static/bidder-info/advangelists.yaml @@ -1,5 +1,5 @@ maintainer: - email: "lokesh@advangelists.com" + email: "prebid@advangelists.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml index 14d9a45f268..f913be6da8c 100644 --- a/static/bidder-info/brightroll.yaml +++ b/static/bidder-info/brightroll.yaml @@ -1,5 +1,5 @@ maintainer: - email: "smithaa@oath.com" + email: "dsp-supply-prebid@verizonmedia.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index ce67700e380..017f0e0c57e 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -1,5 +1,5 @@ maintainer: - email: "mediapsr@conversantmedia.com" + email: "CNVR_PublisherIntegration@conversantmedia.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml index 9bf7e780914..43f00a63eae 100644 --- a/static/bidder-info/datablocks.yaml +++ b/static/bidder-info/datablocks.yaml @@ -1,5 +1,5 @@ maintainer: - email: "henry@datablocks.net" + email: "prebid@datablocks.net" capabilities: app: mediaTypes: diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index d2f7476235f..57c359e451d 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -1,5 +1,5 @@ maintainer: - email: "admin@engagebdr.com" + email: "tech@engagebdr.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml index 71120ed057e..c3ed3ff10e4 100644 --- a/static/bidder-info/gamoshi.yaml +++ b/static/bidder-info/gamoshi.yaml @@ -1,5 +1,5 @@ maintainer: - email: "moses@gamoshi.com" + email: "dev@gamoshi.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index b8a3981c9f0..0feca7cdf73 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -1,5 +1,5 @@ maintainer: - email: "pubtech@gumgum.com" + email: "prebid@gumgum.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index ce2b67db7da..e3062b54fba 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -1,5 +1,5 @@ maintainer: - email: "team-openx@openx.com" + email: "prebid@openx.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index 4b899eb3e56..f15af6ca2e1 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,5 +1,5 @@ maintainer: - email: "inventory.devel@rtbhouse.com" + email: "prebid@rtbhouse.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index f49fa2812b0..6d39319a9f5 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -1,5 +1,5 @@ maintainer: - email: "apex@sonobi.com" + email: "apex.prebid@sonobi.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index da5725eec34..024cafadec0 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -1,6 +1,6 @@ maintainer: - email: "hb-fe-tech@verizonmedia.com" + email: "dsp-supply-prebid@verizonmedia.com" capabilities: site: mediaTypes: - - banner \ No newline at end of file + - banner diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index f404a013337..b6a16e4c2d0 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -1,5 +1,5 @@ maintainer: - email: "service@yoc.com" + email: "supply.partners@yoc.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index 7d6c0af67cd..514f17455ea 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -1,5 +1,5 @@ maintainer: - email: "progsupport@yieldmo.com" + email: "prebid@yieldmo.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/zeroclickfraud.yaml b/static/bidder-info/zeroclickfraud.yaml index 9bf7e780914..527c0065600 100644 --- a/static/bidder-info/zeroclickfraud.yaml +++ b/static/bidder-info/zeroclickfraud.yaml @@ -1,5 +1,5 @@ maintainer: - email: "henry@datablocks.net" + email: "support@datablocks.net" capabilities: app: mediaTypes: From fb59f73a044e5f15d31e9ae8e9cc81eea62e6fa9 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 14 Apr 2020 12:32:07 -0400 Subject: [PATCH 049/603] Add kidoz bidder info (#1257) got this info from email communication with kidoz --- docs/bidders/kidoz.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/bidders/kidoz.md diff --git a/docs/bidders/kidoz.md b/docs/bidders/kidoz.md new file mode 100644 index 00000000000..433dd71c2ca --- /dev/null +++ b/docs/bidders/kidoz.md @@ -0,0 +1,9 @@ +# Kidoz Bidder + +Kidoz is exclusively for Mobile app COPPA compatible ads, 100% kid relevant and appropriate. + +In order for a company to receive bids from Kidoz, they must first open a publisher account at Kidoz.net +(https://accounts.kidoz.net/publishers/register) and accept the Kidoz Terms and Conditions and Privacy Policy. +Kidoz publishers must confirm that all of their content properties are COPPA and GDPR compliant and perform no monitoring +or tracking of U13 users in their operations. New publishers are provided a Publisher ID and AccessToken, this can also +be used to login to their dashboard at the Kidoz.net portal to monitor their account activity. From c027bac4f780a33d3bd0645949e88d06e6fd3c6e Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Tue, 14 Apr 2020 21:49:38 +0300 Subject: [PATCH 050/603] Add Cropping of BAdv for Rubicon Adapter (#1254) * Add Cropping of BAdv for Rubicon Adapter BAdv size is limited to 50 * Fix after review Co-authored-by: Harbar Dmytro --- adapters/rubicon/rubicon.go | 9 +++++++ adapters/rubicon/rubicon_test.go | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 46caf262108..dad85ee1184 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -21,6 +21,8 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) +const badvLimitSize = 50 + type RubiconAdapter struct { http *adapters.HTTPAdapter URI string @@ -740,6 +742,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap request.App = &appCopy } + reqBadv := request.BAdv + if reqBadv != nil { + if len(reqBadv) > badvLimitSize { + request.BAdv = reqBadv[:badvLimitSize] + } + } + request.Imp = []openrtb.Imp{thisImp} request.Cur = nil request.Ext = nil diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index d386daed5b1..96623659d08 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strconv" "testing" "time" @@ -1133,6 +1134,48 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { "Unexpected dfp_ad_unit_code: %s", rubiconExtInventory["dfp_ad_unit_code"]) } +func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { + SIZE_ID := getTestSizes() + bidder := new(RubiconAdapter) + + badvOverflowed := make([]string, 100) + for i := range badvOverflowed { + badvOverflowed[i] = strconv.Itoa(i) + } + + request := &openrtb.BidRequest{ + ID: "test-request-id", + BAdv: badvOverflowed, + Imp: []openrtb.Imp{{ + ID: "test-imp-id", + Banner: &openrtb.Banner{ + Format: []openrtb.Format{ + SIZE_ID[15], + }, + }, + Ext: json.RawMessage(`{ + "bidder": { + "zoneId": 8394, + "siteId": 283282, + "accountId": 7891, + "inventory": {"key1" : "val1"}, + "visitor": {"key2" : "val2"} + } + }`), + }}, + } + + reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) + + rubiconReq := &openrtb.BidRequest{} + if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { + t.Fatalf("Unexpected error while decoding request: %s", err) + } + + badvRequest := rubiconReq.BAdv + assert.Equal(t, badvOverflowed[:50], badvRequest, "Unexpected dfp_ad_unit_code: %s") +} + func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) From d416035355bd7293f54f195a78f386a827b95239 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Wed, 15 Apr 2020 08:33:43 -0700 Subject: [PATCH 051/603] Added metrics support to endpoint aspect (#1226) Co-authored-by: Veronika Solovei --- pbsmetrics/config/metrics.go | 11 ++++ pbsmetrics/config/metrics_test.go | 6 ++ pbsmetrics/go_metrics.go | 18 ++++++ pbsmetrics/go_metrics_test.go | 3 + pbsmetrics/metrics.go | 13 ++-- pbsmetrics/metrics_mock.go | 5 ++ pbsmetrics/prometheus/preload.go | 8 +++ pbsmetrics/prometheus/prometheus.go | 24 ++++++++ pbsmetrics/prometheus/prometheus_test.go | 60 +++++++++++++++++++ router/aspects/request_timeout_handler.go | 8 ++- .../aspects/request_timeout_handler_test.go | 44 ++++++-------- router/router.go | 3 +- 12 files changed, 170 insertions(+), 33 deletions(-) diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index 81cfbfd0798..e1cdaceb0e5 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -181,6 +181,13 @@ func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(success bool, length } } +// RecordRequestQueueTime across all engines +func (me *MultiMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) { + for _, thisME := range *me { + thisME.RecordRequestQueueTime(success, requestType, length) + } +} + // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) type DummyMetricsEngine struct{} @@ -251,3 +258,7 @@ func (me *DummyMetricsEngine) RecordStoredImpCacheResult(cacheResult pbsmetrics. // RecordPrebidCacheRequestTime as a noop func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { } + +// RecordRequestQueueTime as a noop +func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) { +} diff --git a/pbsmetrics/config/metrics_test.go b/pbsmetrics/config/metrics_test.go index ad817ba75a9..d2374f95195 100644 --- a/pbsmetrics/config/metrics_test.go +++ b/pbsmetrics/config/metrics_test.go @@ -115,6 +115,9 @@ func TestMultiMetricsEngine(t *testing.T) { for i := 0; i < 3; i++ { metricsEngine.RecordImps(impTypeLabels) } + + metricsEngine.RecordRequestQueueTime(false, pbsmetrics.ReqTypeVideo, time.Duration(1)) + //Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][pbsmetrics.RequestStatusXX] with the new boolean values added to pbsmetrics.Labels VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeORTB2Web][pbsmetrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "RequestStatuses.Legacy.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeLegacy][pbsmetrics.RequestStatusOK].Count(), 0) @@ -148,6 +151,9 @@ func TestMultiMetricsEngine(t *testing.T) { } VerifyMetrics(t, "AdapterMetrics.AppNexus.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GotBidsMeter.Count(), 0) VerifyMetrics(t, "AdapterMetrics.AppNexus.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].NoBidMeter.Count(), 5) + + VerifyMetrics(t, "RecordRequestQueueTime.Video.Rejected", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][false].Count(), 1) + VerifyMetrics(t, "RecordRequestQueueTime.Video.Accepted", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][true].Count(), 0) } func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) { diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index 9b3dd65ff4e..ff3d9681fb1 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -24,6 +24,7 @@ type Metrics struct { SafariRequestMeter metrics.Meter SafariNoCookieMeter metrics.Meter RequestTimer metrics.Timer + RequestsQueueTimer map[RequestType]map[bool]metrics.Timer PrebidCacheRequestTimerSuccess metrics.Timer PrebidCacheRequestTimerError metrics.Timer StoredReqCacheMeter map[CacheResult]metrics.Meter @@ -111,6 +112,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa SafariRequestMeter: blankMeter, SafariNoCookieMeter: blankMeter, RequestTimer: blankTimer, + RequestsQueueTimer: make(map[RequestType]map[bool]metrics.Timer), PrebidCacheRequestTimerSuccess: blankTimer, PrebidCacheRequestTimerError: blankTimer, StoredReqCacheMeter: make(map[CacheResult]metrics.Meter), @@ -146,6 +148,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa } } + //to minimize memory usage, queuedTimeout metric is now supported for video endpoint only + //boolean value represents 2 general request statuses: accepted and rejected + newMetrics.RequestsQueueTimer["video"] = make(map[bool]metrics.Timer) + newMetrics.RequestsQueueTimer["video"][true] = blankTimer + newMetrics.RequestsQueueTimer["video"][false] = blankTimer return newMetrics } @@ -191,11 +198,15 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d statusMap[stat] = metrics.GetOrRegisterMeter("requests."+string(stat)+"."+string(typ), registry) } } + for _, cacheRes := range CacheResults() { newMetrics.StoredReqCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_request_cache_%s", string(cacheRes)), registry) newMetrics.StoredImpCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_imp_cache_%s", string(cacheRes)), registry) } + newMetrics.RequestsQueueTimer["video"][true] = metrics.GetOrRegisterTimer("queued_requests.video.accepted", registry) + newMetrics.RequestsQueueTimer["video"][false] = metrics.GetOrRegisterTimer("queued_requests.video.rejected", registry) + newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry) newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry) return newMetrics @@ -526,6 +537,13 @@ func (me *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Durati } } +func (me *Metrics) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) { + if requestType == ReqTypeVideo { //remove this check when other request types are supported + me.RequestsQueueTimer[requestType][success].Update(length) + } + +} + func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) { met, ok := meters[bidder] if ok { diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index b403733dcc7..253ff69e3c2 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -50,6 +50,9 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "requests.badinput.video", m.RequestStatuses[ReqTypeVideo][RequestStatusBadInput]) ensureContains(t, registry, "requests.err.video", m.RequestStatuses[ReqTypeVideo][RequestStatusErr]) ensureContains(t, registry, "requests.networkerr.video", m.RequestStatuses[ReqTypeVideo][RequestStatusNetworkErr]) + + ensureContains(t, registry, "queued_requests.video.rejected", m.RequestsQueueTimer[ReqTypeVideo][false]) + ensureContains(t, registry, "queued_requests.video.accepted", m.RequestsQueueTimer[ReqTypeVideo][true]) } func TestRecordBidType(t *testing.T) { diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index cc836011efa..611692c9c01 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -154,11 +154,12 @@ func CookieTypes() []CookieFlag { // Request/return status const ( - RequestStatusOK RequestStatus = "ok" - RequestStatusBadInput RequestStatus = "badinput" - RequestStatusErr RequestStatus = "err" - RequestStatusNetworkErr RequestStatus = "networkerr" - RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp" + RequestStatusOK RequestStatus = "ok" + RequestStatusBadInput RequestStatus = "badinput" + RequestStatusErr RequestStatus = "err" + RequestStatusNetworkErr RequestStatus = "networkerr" + RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp" + RequestStatusQueueTimeout RequestStatus = "queuetimeout" ) func RequestStatuses() []RequestStatus { @@ -168,6 +169,7 @@ func RequestStatuses() []RequestStatus { RequestStatusErr, RequestStatusNetworkErr, RequestStatusBlacklisted, + RequestStatusQueueTimeout, } } @@ -272,4 +274,5 @@ type MetricsEngine interface { RecordStoredReqCacheResult(cacheResult CacheResult, inc int) RecordStoredImpCacheResult(cacheResult CacheResult, inc int) RecordPrebidCacheRequestTime(success bool, length time.Duration) + RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) } diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index 6d57f9fcfaa..1f5b84b1e0f 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -96,3 +96,8 @@ func (me *MetricsEngineMock) RecordStoredImpCacheResult(cacheResult CacheResult, func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length time.Duration) { me.Called(success, length) } + +// RecordRequestQueueTime mock +func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) { + me.Called(success, requestType, length) +} diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go index 7654dd54f82..11e6bdc14d8 100644 --- a/pbsmetrics/prometheus/preload.go +++ b/pbsmetrics/prometheus/preload.go @@ -1,6 +1,7 @@ package prometheusmetrics import ( + "github.com/prebid/prebid-server/pbsmetrics" "github.com/prometheus/client_golang/prometheus" ) @@ -91,6 +92,13 @@ func preloadLabelValues(m *Metrics) { adapterLabel: adapterValues, actionLabel: actionValues, }) + + //to minimize memory usage, queuedTimeout metric is now supported for video endpoint only + //boolean value represents 2 general request statuses: accepted and rejected + preloadLabelValuesForHistogram(m.requestsQueueTimer, map[string][]string{ + requestTypeLabel: {string(pbsmetrics.ReqTypeVideo)}, + requestStatusLabel: {requestSuccessLabel, requestRejectLabel}, + }) } func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index e2b646d5238..d66defea4cd 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -24,6 +24,7 @@ type Metrics struct { prebidCacheWriteTimer *prometheus.HistogramVec requests *prometheus.CounterVec requestsTimer *prometheus.HistogramVec + requestsQueueTimer *prometheus.HistogramVec requestsWithoutCookie *prometheus.CounterVec storedImpressionsCacheResult *prometheus.CounterVec storedRequestCacheResult *prometheus.CounterVec @@ -73,11 +74,17 @@ const ( markupDeliveryNurl = "nurl" ) +const ( + requestSuccessLabel = "requestAcceptedLabel" + requestRejectLabel = "requestRejectedLabel" +) + // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. func NewMetrics(cfg config.PrometheusMetrics) *Metrics { requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} + queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300} metrics := Metrics{} metrics.Registry = prometheus.NewRegistry() @@ -187,6 +194,12 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of total requests to Prebid Server labeled by account.", []string{accountLabel}) + metrics.requestsQueueTimer = newHistogram(cfg, metrics.Registry, + "request_queue_time", + "Seconds request was waiting in queue", + []string{requestTypeLabel, requestStatusLabel}, + queuedRequestTimeBuckets) + preloadLabelValues(&metrics) return &metrics @@ -374,3 +387,14 @@ func (m *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duratio successLabel: strconv.FormatBool(success), }).Observe(length.Seconds()) } + +func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) { + successLabelFormatted := requestRejectLabel + if success { + successLabelFormatted = requestSuccessLabel + } + m.requestsQueueTimer.With(prometheus.Labels{ + requestTypeLabel: string(requestType), + requestStatusLabel: successLabelFormatted, + }).Observe(length.Seconds()) +} diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index f76480f0852..e4d6a4f78d1 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -881,6 +881,48 @@ func TestMetricAccumulationSpotCheck(t *testing.T) { expectedValue) } +func TestRecordRequestQueueTimeMetric(t *testing.T) { + performTest := func(m *Metrics, requestStatus bool, requestType pbsmetrics.RequestType, timeInSec float64) { + m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) + } + + testCases := []struct { + description string + status string + testCase func(m *Metrics) + expectedCount uint64 + expectedSum float64 + }{ + { + description: "Success", + status: requestSuccessLabel, + testCase: func(m *Metrics) { + performTest(m, true, pbsmetrics.ReqTypeVideo, 2) + }, + expectedCount: 1, + expectedSum: 2, + }, + { + description: "TimeoutError", + status: requestRejectLabel, + testCase: func(m *Metrics) { + performTest(m, false, pbsmetrics.ReqTypeVideo, 50) + }, + expectedCount: 1, + expectedSum: 50, + }, + } + + m := createMetricsForTesting() + for _, test := range testCases { + + test.testCase(m) + + result := getHistogramFromHistogramVecByTwoKeys(m.requestsQueueTimer, requestTypeLabel, "video", requestStatusLabel, test.status) + assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) + } +} + func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { m := dto.Metric{} counter.Write(&m) @@ -906,6 +948,24 @@ func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, return result } +func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, label1Key, label1Value, label2Key, label2Value string) dto.Histogram { + var result dto.Histogram + processMetrics(histogram, func(m dto.Metric) { + for ind, label := range m.GetLabel() { + if label.GetName() == label1Key && label.GetValue() == label1Value { + valInd := ind + if ind == 1 { + valInd = 0 + } + if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value { + result = *m.GetHistogram() + } + } + } + }) + return result +} + func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) { collectorChan := make(chan prometheus.Metric) go func() { diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go index ae11f8c5614..23d6cef9faf 100644 --- a/router/aspects/request_timeout_handler.go +++ b/router/aspects/request_timeout_handler.go @@ -3,11 +3,13 @@ package aspects import ( "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbsmetrics" "net/http" "strconv" + "time" ) -func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders) httprouter.Handle { +func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine pbsmetrics.MetricsEngine, requestType pbsmetrics.RequestType) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { @@ -30,13 +32,17 @@ func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestT return } + reqTimeDuration := time.Duration(reqTimeFloat * float64(time.Second)) + //Return HTTP 408 if requests stays too long in queue if reqTimeFloat >= reqTimeoutFloat { w.WriteHeader(http.StatusRequestTimeout) w.Write([]byte("Queued request processing time exceeded maximum")) + metricsEngine.RecordRequestQueueTime(false, requestType, reqTimeDuration) return } + metricsEngine.RecordRequestQueueTime(true, requestType, reqTimeDuration) f(w, r, params) } diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go index 5283d5d51e7..cdc920c4263 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -1,12 +1,14 @@ package aspects import ( + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbsmetrics" "net/http" "net/http/httptest" + "strconv" "testing" - - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" + "time" "github.com/stretchr/testify/assert" ) @@ -23,6 +25,7 @@ func TestAny(t *testing.T) { expectedRespCodeMessage string expectedRespBody string expectedRespBodyMessage string + requestStatusMetrics bool }{ { //TestQueuedRequestTimeoutWithTimeout @@ -33,6 +36,7 @@ func TestAny(t *testing.T) { expectedRespCodeMessage: "Http response code is incorrect, should be 408", expectedRespBody: "Queued request processing time exceeded maximum", expectedRespBodyMessage: "Body should have error message", + requestStatusMetrics: false, }, { //TestQueuedRequestTimeoutNoTimeout @@ -43,6 +47,7 @@ func TestAny(t *testing.T) { expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", + requestStatusMetrics: true, }, { //TestQueuedRequestNoHeaders @@ -53,6 +58,7 @@ func TestAny(t *testing.T) { expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", + requestStatusMetrics: true, }, { //TestQueuedRequestSomeHeaders @@ -63,31 +69,13 @@ func TestAny(t *testing.T) { expectedRespCodeMessage: "Http response code is incorrect, should be 200", expectedRespBody: "Executed", expectedRespBodyMessage: "Body should be present in response", - }, - { - //TestQueuedRequestAllHeadersIncorrect - reqTimeInQueue: "test1", - reqTimeOut: "test2", - setHeaders: true, - expectedRespCode: http.StatusInternalServerError, - expectedRespCodeMessage: "Http response code is incorrect, should be 400", - expectedRespBody: "Request timeout headers are incorrect (wrong format)", - expectedRespBodyMessage: "Body should have error message", - }, - { - //TestQueuedRequestSomeHeadersIncorrect - reqTimeInQueue: "test1", - reqTimeOut: "123", - setHeaders: true, - expectedRespCode: http.StatusInternalServerError, - expectedRespCodeMessage: "Http response code is incorrect, should be 400", - expectedRespBody: "Request timeout headers are incorrect (wrong format)", - expectedRespBodyMessage: "Body should have error message", + requestStatusMetrics: true, }, } for _, test := range testCases { - result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders) + reqTimeFloat, _ := strconv.ParseFloat(test.reqTimeInQueue, 64) + result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders, pbsmetrics.ReqTypeVideo, test.requestStatusMetrics, reqTimeFloat) assert.Equal(t, test.expectedRespCode, result.Code, test.expectedRespCodeMessage) assert.Equal(t, test.expectedRespBody, string(result.Body.Bytes()), test.expectedRespBodyMessage) } @@ -101,7 +89,7 @@ func MockHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Write([]byte("Executed")) } -func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, setHeaders bool) *httptest.ResponseRecorder { +func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, setHeaders bool, requestType pbsmetrics.RequestType, status bool, requestDuration float64) *httptest.ResponseRecorder { rw := httptest.NewRecorder() req, err := http.NewRequest("POST", "/test", nil) if err != nil { @@ -114,7 +102,11 @@ func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, s customHeaders := config.RequestTimeoutHeaders{reqTimeInQueueHeaderName, reqTimeoutHeaderName} - handler := QueuedRequestTimeout(MockEndpoint(), customHeaders) + metrics := &pbsmetrics.MetricsEngineMock{} + + metrics.On("RecordRequestQueueTime", status, requestType, time.Duration(requestDuration*float64(time.Second))).Once() + + handler := QueuedRequestTimeout(MockEndpoint(), customHeaders, metrics, requestType) r := httprouter.New() r.POST("/test", handler) diff --git a/router/router.go b/router/router.go index 8ac463b85a0..68627f937dd 100644 --- a/router/router.go +++ b/router/router.go @@ -6,6 +6,7 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/prebid/prebid-server/pbsmetrics" "io/ioutil" "net/http" "path/filepath" @@ -258,7 +259,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r requestTimeoutHeaders := config.RequestTimeoutHeaders{} if cfg.RequestTimeoutHeaders != requestTimeoutHeaders { - videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders) + videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, pbsmetrics.ReqTypeVideo) } r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges)) From cc7a247bee26b46c53d6bf7473b9146ce8f8ef57 Mon Sep 17 00:00:00 2001 From: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Date: Wed, 15 Apr 2020 10:21:16 -0700 Subject: [PATCH 052/603] Prebid Server adapter for Telaria (#1231) * TELARIA adapter. First Pass * Some refactoring * added the json files * fixed some tests and added the bidder info * fixed some tests and added the bidder info * added default user sync ur; * - Handling gzipped responses from our server * - more refactoring. * added the proper user sync default URL * changed the urls from dev to prod * changed up the required fields. Now AdCode in the Imp.Ext isn't required but Bid.SeatCode is required * change in the return type after decompressing * some refactoring * change in our config url * using pbs.yml to switch between our production and test URLs * setting default endpoint * - fixed the issue that was preventing telaria test cases to run. - added more test cases * - Modifications as per the changes requested by the maintainers. * Moved the seat code to imp.ext * Moved the seat code to imp.ext * Added 'Telaria: ' prefix for error messages * - Fixes for race conditions. Was modifying the original request object instead of a copy * cosmetic changes. * added params_test.go Co-authored-by: Vinay Prasad --- adapters/telaria/params_test.go | 50 +++ adapters/telaria/telaria.go | 330 ++++++++++++++++++ adapters/telaria/telaria_test.go | 34 ++ .../telariatest/exemplary/video-app.json | 157 +++++++++ .../telariatest/exemplary/video-web.json | 145 ++++++++ .../telariatest/params/race/video.json | 4 + .../supplemental/banner-unsupported.json | 42 +++ .../supplemental/invalid-response.json | 105 ++++++ .../invalid-telaria-ext-object.json | 29 ++ .../supplemental/requires-imp-object.json | 16 + .../supplemental/requires-seat-code.json | 30 ++ .../supplemental/requires-video-object.json | 26 ++ .../supplemental/status-code-bad-request.json | 80 +++++ .../supplemental/status-code-no-content.json | 83 +++++ .../supplemental/status-code-other-error.json | 83 +++++ .../status-code-service-unavailable.json | 83 +++++ adapters/telaria/usersync.go | 12 + adapters/telaria/usersync_test.go | 33 ++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_telaria.go | 6 + static/bidder-info/telaria.yaml | 9 + static/bidder-params/telaria.json | 22 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 1388 insertions(+) create mode 100644 adapters/telaria/params_test.go create mode 100644 adapters/telaria/telaria.go create mode 100644 adapters/telaria/telaria_test.go create mode 100644 adapters/telaria/telariatest/exemplary/video-app.json create mode 100644 adapters/telaria/telariatest/exemplary/video-web.json create mode 100644 adapters/telaria/telariatest/params/race/video.json create mode 100644 adapters/telaria/telariatest/supplemental/banner-unsupported.json create mode 100644 adapters/telaria/telariatest/supplemental/invalid-response.json create mode 100644 adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json create mode 100644 adapters/telaria/telariatest/supplemental/requires-imp-object.json create mode 100644 adapters/telaria/telariatest/supplemental/requires-seat-code.json create mode 100644 adapters/telaria/telariatest/supplemental/requires-video-object.json create mode 100644 adapters/telaria/telariatest/supplemental/status-code-bad-request.json create mode 100644 adapters/telaria/telariatest/supplemental/status-code-no-content.json create mode 100644 adapters/telaria/telariatest/supplemental/status-code-other-error.json create mode 100644 adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json create mode 100644 adapters/telaria/usersync.go create mode 100644 adapters/telaria/usersync_test.go create mode 100644 openrtb_ext/imp_telaria.go create mode 100644 static/bidder-info/telaria.yaml create mode 100644 static/bidder-params/telaria.json diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go new file mode 100644 index 00000000000..efa3fba1be9 --- /dev/null +++ b/adapters/telaria/params_test.go @@ -0,0 +1,50 @@ +package telaria + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTelaria, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Telaria params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Telaria schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTelaria, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"adCode": "string", "seatCode": "string", "originalPublisherid": "string"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "originalPublisherid": "string"}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, +} diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go new file mode 100644 index 00000000000..9edafa86a32 --- /dev/null +++ b/adapters/telaria/telaria.go @@ -0,0 +1,330 @@ +package telaria + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" +) + +const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" + +type TelariaAdapter struct { + URI string +} + +// This will be part of Imp[i].Ext when this adapter calls out the Telaria Ad Server +type ImpressionExtOut struct { + OriginalTagID string `json:"originalTagid"` + OriginalPublisherID string `json:"originalPublisherid"` +} + +// used for cookies and such +func (a *TelariaAdapter) Name() string { + return "telaria" +} + +func (a *TelariaAdapter) SkipNoCookies() bool { + return false +} + +// Endpoint for Telaria Ad server +func (a *TelariaAdapter) FetchEndpoint() string { + return a.URI +} + +// Checker method to ensure len(request.Imp) > 0 +func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { + if len(request.Imp) == 0 { + err := &errortypes.BadInput{ + Message: "Telaria: Missing Imp Object", + } + return err + } + return nil +} + +// Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist +func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error { + hasVideoObject := false + + for _, imp := range request.Imp { + if imp.Banner != nil { + return &errortypes.BadInput{ + Message: "Telaria: Banner not supported", + } + } + + hasVideoObject = hasVideoObject || imp.Video != nil + } + + if !hasVideoObject { + return &errortypes.BadInput{ + Message: "Telaria: Only Supports Video", + } + } + + return nil +} + +// Fetches the populated header object +func GetHeaders(request *openrtb.BidRequest) *http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + headers.Add("Accept-Encoding", "gzip") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + + if len(request.Device.Language) > 0 { + headers.Add("Accept-Language", request.Device.Language) + } + + if request.Device.DNT != nil { + headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT))) + } + } + + return &headers +} + +// Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format +func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpTelaria, error) { + var bidderExt adapters.ExtImpBidder + err := json.Unmarshal(imp.Ext, &bidderExt) + + if err != nil { + err = &errortypes.BadInput{ + Message: "Telaria: ext.bidder not provided", + } + + return nil, err + } + + var telariaExt openrtb_ext.ExtImpTelaria + err = json.Unmarshal(bidderExt.Bidder, &telariaExt) + + if err != nil { + return nil, err + } + + if telariaExt.SeatCode == "" { + return nil, &errortypes.BadInput{Message: "Telaria: Seat Code required"} + } + + return &telariaExt, nil +} + +// Method to fetch the original publisher ID. Note that this method must be called +// before we replace publisher.ID with seatCode +func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) string { + + if request.Site != nil && request.Site.Publisher != nil { + return request.Site.Publisher.ID + } else if request.App != nil && request.App.Publisher != nil { + return request.App.Publisher.ID + } + + return "" +} + +// Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID +func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb.Publisher) *openrtb.Publisher { + var pub = &openrtb.Publisher{ID: seatCode} + + if publisher != nil { + pub.Domain = publisher.Domain + pub.Name = publisher.Name + pub.Cat = publisher.Cat + pub.Ext = publisher.Ext + } + + return pub +} + +// This method changes .publisher.id to the seatCode +func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCode string) (*openrtb.Site, *openrtb.App) { + if request.Site != nil { + siteCopy := *request.Site + siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher) + return &siteCopy, nil + } else if request.App != nil { + appCopy := *request.App + appCopy.Publisher = a.MakePublisherObject(seatCode, request.App.Publisher) + return nil, &appCopy + } + return nil, nil +} + +func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + // make a copy of the incoming request + request := *requestIn + + // ensure that the request has Impressions + if noImps := a.CheckHasImps(&request); noImps != nil { + return nil, []error{noImps} + } + + // ensure that the request has a Video object + if noVideoObjectError := a.CheckHasVideoObject(&request); noVideoObjectError != nil { + return nil, []error{noVideoObjectError} + } + + var seatCode string + originalPublisherID := a.FetchOriginalPublisherID(&request) + + var errors []error + for i, imp := range request.Imp { + // fetch adCode & seatCode from Imp[i].Ext + telariaExt, err := a.FetchTelariaExtImpParams(&imp) + if err != nil { + errors = append(errors, err) + break + } + + seatCode = telariaExt.SeatCode + + // move the original tagId and the original publisher.id into the Imp[i].Ext object + request.Imp[i].Ext, err = json.Marshal(&ImpressionExtOut{request.Imp[i].TagID, originalPublisherID}) + if err != nil { + errors = append(errors, err) + break + } + + // Swap the tagID with adCode + request.Imp[i].TagID = telariaExt.AdCode + } + + if len(errors) > 0 { + return nil, errors + } + + // Add seatCode to .Publisher.ID + siteObject, appObject := a.PopulatePublisherId(&request, seatCode) + + request.Site = siteObject + request.App = appObject + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.FetchEndpoint(), + Body: reqJSON, + Headers: *GetHeaders(&request), + }}, nil +} + +// response isn't automatically decompressed. This method unzips the response if Content-Encoding is gzip +func GetResponseBody(response *adapters.ResponseData) ([]byte, error) { + + if "gzip" == response.Headers.Get("Content-Encoding") { + body := bytes.NewBuffer(response.Body) + r, readerErr := gzip.NewReader(body) + if readerErr != nil { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode), + } + } + var resB bytes.Buffer + var err error + _, err = resB.ReadFrom(r) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode), + } + } + + response.Headers.Del("Content-Encoding") + + return resB.Bytes(), nil + } else { + return response.Body, nil + } +} + +func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusNoContent { + return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"} + } + + if response.StatusCode == http.StatusBadRequest { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + return nil +} + +func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + httpStatusError := a.CheckResponseStatusCodes(response) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + responseBody, err := GetResponseBody(response) + + if err != nil { + return nil, []error{err} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Telaria: Bad Server Response", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + + for _, bid := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeVideo, + }) + } + return bidResponse, nil +} + +func NewTelariaBidder(endpoint string) *TelariaAdapter { + if endpoint == "" { + endpoint = Endpoint + } + + return &TelariaAdapter{ + URI: endpoint, + } +} diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go new file mode 100644 index 00000000000..7ad96b9307b --- /dev/null +++ b/adapters/telaria/telaria_test.go @@ -0,0 +1,34 @@ +package telaria + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +/** + * Verify adapter names are setup correctly. + */ +func TestTelariaAdapterNames(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyStringValue(adapter.Name(), "telaria", t) +} + +/** + * Verify adapter SkipNoCookie is correct. + */ +func TestTelariaAdapterSkipNoCookiesFlag(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) +} + +/** + * Verify bidder has the proper URL + */ +func TestTelariaAdapterEndpoint(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyStringValue(adapter.URI, "https://ads.tremorhub.com/ad/rtb/prebid", t) +} + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "telariatest", NewTelariaBidder("")) +} diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json new file mode 100644 index 00000000000..09bcf998454 --- /dev/null +++ b/adapters/telaria/telariatest/exemplary/video-app.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "my-adcode", + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [{ + "bid": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }], + "seat": "telaria" + }], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } + } + } + }], + "expectedBids": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }] +} diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json new file mode 100644 index 00000000000..5dc26b0f018 --- /dev/null +++ b/adapters/telaria/telariatest/exemplary/video-web.json @@ -0,0 +1,145 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [{ + "bid": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }], + "seat": "telaria" + }], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } + } + } + }], + "expectedBids": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }] +} diff --git a/adapters/telaria/telariatest/params/race/video.json b/adapters/telaria/telariatest/params/race/video.json new file mode 100644 index 00000000000..e3b67ec8c20 --- /dev/null +++ b/adapters/telaria/telariatest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "adCode": "my-adcode", + "seatCode": "my-seatcode" +} diff --git a/adapters/telaria/telariatest/supplemental/banner-unsupported.json b/adapters/telaria/telariatest/supplemental/banner-unsupported.json new file mode 100644 index 00000000000..1e75371dd22 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/banner-unsupported.json @@ -0,0 +1,42 @@ + +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Banner not supported", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.com", + "publisher": { + "id": "someother-publisher-id" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + } +} diff --git a/adapters/telaria/telariatest/supplemental/invalid-response.json b/adapters/telaria/telariatest/supplemental/invalid-response.json new file mode 100644 index 00000000000..8e4f1ee8cb5 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/invalid-response.json @@ -0,0 +1,105 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json b/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json new file mode 100644 index 00000000000..efdec8ad61b --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json @@ -0,0 +1,29 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: ext.bidder not provided", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "Awesome" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-imp-object.json b/adapters/telaria/telariatest/supplemental/requires-imp-object.json new file mode 100644 index 00000000000..5933dc4ee08 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-imp-object.json @@ -0,0 +1,16 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Missing Imp Object", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-seat-code.json b/adapters/telaria/telariatest/supplemental/requires-seat-code.json new file mode 100644 index 00000000000..5c05e529772 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-seat-code.json @@ -0,0 +1,30 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Seat Code required", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-1", + "video": { + "w": 640, + "h": 480, + "linearity": 1 + }, + "ext": { + "bidder": { + "adCode": "my-adcode" + } + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-video-object.json b/adapters/telaria/telariatest/supplemental/requires-video-object.json new file mode 100644 index 00000000000..3f797c9e1de --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-video-object.json @@ -0,0 +1,26 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Only Supports Video", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-1", + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-bad-request.json b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..0b5d8a85982 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json @@ -0,0 +1,80 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "" + } + } + ], + "app": { + "id": "123456789", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Unexpected status code: [ 400 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-no-content.json b/adapters/telaria/telariatest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..ffb183f4121 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-no-content.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Invalid Bid Request received by the server", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-other-error.json b/adapters/telaria/telariatest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..15e4b7f87d8 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-other-error.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Something went wrong, please contact your Account Manager. Status Code: [ 306 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..b92d4ea8ba1 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Something went wrong, please contact your Account Manager. Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/usersync.go b/adapters/telaria/usersync.go new file mode 100644 index 00000000000..e3f76f6e9b4 --- /dev/null +++ b/adapters/telaria/usersync.go @@ -0,0 +1,12 @@ +package telaria + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTelariaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("telaria", 202, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/telaria/usersync_test.go b/adapters/telaria/usersync_test.go new file mode 100644 index 00000000000..4896b253d2f --- /dev/null +++ b/adapters/telaria/usersync_test.go @@ -0,0 +1,33 @@ +package telaria + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTelariaSyncer(t *testing.T) { + + syncURL := "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTelariaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://pbs.publishers.tremorhub.com/pubsync?gdpr=0&gdpr_consent=", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 202, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) + assert.Equal(t, "telaria", syncer.FamilyName()) + +} diff --git a/config/config.go b/config/config.go index 2cb5f8f2e66..652ad28cd87 100644 --- a/config/config.go +++ b/config/config.go @@ -535,6 +535,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") // openrtb_ext.BidderTappx doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") @@ -729,6 +730,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index f7b970c571b..8e779822cae 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -54,6 +54,7 @@ import ( "github.com/prebid/prebid-server/adapters/sovrn" "github.com/prebid/prebid-server/adapters/synacormedia" "github.com/prebid/prebid-server/adapters/tappx" + "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" @@ -127,6 +128,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderSovrn: sovrn.NewSovrnBidder(client, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), openrtb_ext.BidderSynacormedia: synacormedia.NewSynacorMediaBidder(cfg.Adapters[string(openrtb_ext.BidderSynacormedia)].Endpoint), openrtb_ext.BidderTappx: tappx.NewTappxBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTappx))].Endpoint), + openrtb_ext.BidderTelaria: telaria.NewTelariaBidder(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTelaria))].Endpoint), openrtb_ext.BidderTriplelift: triplelift.NewTripleliftBidder(client, cfg.Adapters[string(openrtb_ext.BidderTriplelift)].Endpoint), openrtb_ext.BidderTripleliftNative: triplelift_native.NewTripleliftNativeBidder(client, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].ExtraAdapterInfo), openrtb_ext.BidderUcfunnel: ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ec9745563ef..43d9b894ea8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -72,6 +72,7 @@ const ( BidderSovrn BidderName = "sovrn" BidderSynacormedia BidderName = "synacormedia" BidderTappx BidderName = "tappx" + BidderTelaria BidderName = "telaria" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" BidderUcfunnel BidderName = "ucfunnel" @@ -135,6 +136,7 @@ var BidderMap = map[string]BidderName{ "sovrn": BidderSovrn, "synacormedia": BidderSynacormedia, "tappx": BidderTappx, + "telaria": BidderTelaria, "triplelift": BidderTriplelift, "triplelift_native": BidderTripleliftNative, "ucfunnel": BidderUcfunnel, diff --git a/openrtb_ext/imp_telaria.go b/openrtb_ext/imp_telaria.go new file mode 100644 index 00000000000..8ea371a8ad0 --- /dev/null +++ b/openrtb_ext/imp_telaria.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpTelaria struct { + AdCode string `json:"adCode,omitempty"` + SeatCode string `json:"seatCode"` +} diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml new file mode 100644 index 00000000000..43e8707a17b --- /dev/null +++ b/static/bidder-info/telaria.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "github@telaria.com" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-params/telaria.json b/static/bidder-params/telaria.json new file mode 100644 index 00000000000..b4121967351 --- /dev/null +++ b/static/bidder-params/telaria.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Telaria Adapter Params", + "description": "A schema which validates params accepted by the Telaria adapter", + + "type": "object", + "properties": { + "adCode": { + "type": "string", + "description": "The Ad Unit Code." + }, + "seatCode": { + "type": "string", + "description": "Your Seat Code." + }, + "originalPublisherid": { + "type": "string", + "description": "publisher ID from the original request" + } + }, + "required": ["seatCode"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index be0392f2dbb..da235402bf0 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -47,6 +47,7 @@ import ( "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" @@ -110,6 +111,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 383e24d82cf..637f590e25f 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -56,6 +56,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSovrn): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, + string(openrtb_ext.BidderTelaria): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, string(openrtb_ext.BidderUcfunnel): syncConfig, From f07b1c335893b7c80a2c58844031885aece8b85b Mon Sep 17 00:00:00 2001 From: Krzysztof Desput Date: Wed, 15 Apr 2020 21:03:10 +0200 Subject: [PATCH 053/603] #615 Beachfront URLs from config (#1238) --- adapters/beachfront/beachfront.go | 80 ++++++++++++------- adapters/beachfront/beachfront_test.go | 2 +- .../exemplary/minimal-banner.json | 2 +- .../beachfronttest/exemplary/simple-mix.json | 2 +- .../minimal-banner-empty_array-200.json | 2 +- .../supplemental/minimal-site-banner.json | 2 +- .../supplemental/mobile-banner.json | 2 +- .../supplemental/multi-banner.json | 2 +- config/config.go | 1 + config/config_test.go | 2 + exchange/adapter_map.go | 17 ++-- exchange/exchange_test.go | 5 ++ 12 files changed, 74 insertions(+), 45 deletions(-) diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index e2eb31b3577..34a198e93a2 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -4,26 +4,27 @@ import ( "encoding/json" "errors" "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" "reflect" "strconv" "strings" + + "github.com/golang/glog" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" ) const Seat = "beachfront" const BidCapacity = 5 -const bannerEndpoint = "https://display.bfmio.com/prebid_display" -const videoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id" +const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id" const nurlVideoEndpointSuffix = "&prebidserver" const beachfrontAdapterName = "BF_PREBID_S2S" -const beachfrontAdapterVersion = "0.8.0" +const beachfrontAdapterVersion = "0.9.0" const minBidFloor = 0.01 @@ -31,6 +32,12 @@ const DefaultVideoWidth = 300 const DefaultVideoHeight = 250 type BeachfrontAdapter struct { + bannerEndpoint string + extraInfo ExtraInfo +} + +type ExtraInfo struct { + VideoEndpoint string `json:"video_endpoint,omitempty"` } type beachfrontRequests struct { @@ -138,7 +145,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a if err == nil { reqs[0] = &adapters.RequestData{ Method: "POST", - Uri: bannerEndpoint, + Uri: a.bannerEndpoint, Body: bytes, Headers: headers, } @@ -159,7 +166,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a if err == nil { reqs[j+nurlBump] = &adapters.RequestData{ Method: "POST", - Uri: videoEndpoint + "=" + beachfrontRequests.ADMVideo[j].AppId, + Uri: a.extraInfo.VideoEndpoint + "=" + beachfrontRequests.ADMVideo[j].AppId, Body: bytes, Headers: headers, } @@ -178,7 +185,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a bytes = append([]byte(`{"isPrebid":true,`), bytes[1:]...) reqs[j+admBump] = &adapters.RequestData{ Method: "POST", - Uri: videoEndpoint + "=" + beachfrontRequests.NurlVideo[j].AppId + nurlVideoEndpointSuffix, + Uri: a.extraInfo.VideoEndpoint + "=" + beachfrontRequests.NurlVideo[j].AppId + nurlVideoEndpointSuffix, Body: bytes, Headers: headers, } @@ -518,13 +525,13 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bids[i], - BidType: getBidType(externalRequest), + BidType: a.getBidType(externalRequest), BidVideo: &impVideo, }) } else { bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bids[i], - BidType: getBidType(externalRequest), + BidType: a.getBidType(externalRequest), }) } } @@ -532,6 +539,15 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, errs } +func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType { + t := strings.Split(externalRequest.Uri, "=")[0] + if t == a.extraInfo.VideoEndpoint { + return openrtb_ext.BidTypeVideo + } + + return openrtb_ext.BidTypeBanner +} + func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { var beachfrontResp []beachfrontResponseSlot var errs = make([]error, 0) @@ -629,13 +645,13 @@ func getBeachfrontExtension(imp openrtb.Imp) (openrtb_ext.ExtImpBeachfront, erro } func getDomain(page string) string { - protoUrl := strings.Split(page, "//") + protoURL := strings.Split(page, "//") var domainPage string - if len(protoUrl) > 1 { - domainPage = protoUrl[1] + if len(protoURL) > 1 { + domainPage = protoURL[1] } else { - domainPage = protoUrl[0] + domainPage = protoURL[0] } return strings.Split(domainPage, "/")[0] @@ -643,9 +659,9 @@ func getDomain(page string) string { } func isSecure(page string) int8 { - protoUrl := strings.Split(page, "://") + protoURL := strings.Split(page, "://") - if len(protoUrl) > 1 && protoUrl[0] == "https" { + if len(protoURL) > 1 && protoURL[0] == "https" { return 1 } @@ -663,19 +679,25 @@ func getIP(ip string) string { return ip } -func getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType { - t := strings.Split(externalRequest.Uri, "=")[0] - if t == videoEndpoint { - return openrtb_ext.BidTypeVideo - } - - return openrtb_ext.BidTypeBanner -} - func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest { return append(slice[:s], slice[s+1:]...) } -func NewBeachfrontBidder() *BeachfrontAdapter { - return &BeachfrontAdapter{} +func NewBeachfrontBidder(bannerEndpoint string, extraAdapterInfo string) adapters.Bidder { + var extraInfo ExtraInfo + + if len(extraAdapterInfo) == 0 { + extraAdapterInfo = "{\"video_endpoint\":\"" + defaultVideoEndpoint + "\"}" + } + + if err := json.Unmarshal([]byte(extraAdapterInfo), &extraInfo); err != nil { + glog.Fatal("Invalid Beachfront extra adapter info: " + err.Error()) + return nil + } + + if extraInfo.VideoEndpoint == "" { + extraInfo.VideoEndpoint = defaultVideoEndpoint + } + + return &BeachfrontAdapter{bannerEndpoint: bannerEndpoint, extraInfo: extraInfo} } diff --git a/adapters/beachfront/beachfront_test.go b/adapters/beachfront/beachfront_test.go index 4e82deaf3d8..c220cebf9b0 100644 --- a/adapters/beachfront/beachfront_test.go +++ b/adapters/beachfront/beachfront_test.go @@ -7,5 +7,5 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "beachfronttest", new(BeachfrontAdapter)) + adapterstest.RunJSONBidderTest(t, "beachfronttest", NewBeachfrontBidder("https://display.bfmio.com/prebid_display", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")) } diff --git a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json index ffcea194cdd..51ce4e9295e 100644 --- a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json @@ -56,7 +56,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.8.0", + "adapterVersion": "0.9.0", "user": { } } diff --git a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json index 6d8e483ee6d..eb5d9b07abc 100644 --- a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json +++ b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json @@ -85,7 +85,7 @@ "buyeruid": "some-buyer" }, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.8.0", + "adapterVersion": "0.9.0", "ip": "192.168.255.255", "requestId": "61b87329-8790-47b7-90dd-c53ae7ce1723" } diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json index f189b2c8c79..7bdbc73cd5e 100644 --- a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json +++ b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json @@ -56,7 +56,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.8.0", + "adapterVersion": "0.9.0", "user": { } } diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json index b610c96f58a..27b24357247 100644 --- a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json @@ -56,7 +56,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.8.0", + "adapterVersion": "0.9.0", "user": { } } diff --git a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json index d47393b7caf..ea38d7adae7 100644 --- a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json @@ -87,7 +87,7 @@ }, "adapterName":"BF_PREBID_S2S", - "adapterVersion":"0.8.0", + "adapterVersion":"0.9.0", "ip":"192.168.255.255", "requestId":"763e3312-19d5-4b07-a61d-890147e863a1" } diff --git a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json index c4120787852..46699511a9c 100644 --- a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json @@ -96,7 +96,7 @@ "dnt": 1, "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.8.0", + "adapterVersion": "0.9.0", "user": { "buyeruid": "some-buyer", "id": "some-user" diff --git a/config/config.go b/config/config.go index 652ad28cd87..1c686591ef2 100644 --- a/config/config.go +++ b/config/config.go @@ -695,6 +695,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs v.SetDefault("adapters.appnexus.platform_id", "5") v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") + v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24") diff --git a/config/config_test.go b/config/config_test.go index 9677ce2aaba..92794d7941e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -270,6 +270,8 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "adapters.audiencenetwork.usersync_url", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].UserSyncURL, "http://facebook.com/ortb/prebid-s2s") cmpStrings(t, "adapters.audiencenetwork.platform_id", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, "abcdefgh1234") cmpStrings(t, "adapters.audiencenetwork.app_secret", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppSecret, "987abc") + cmpStrings(t, "adapters.beachfront.endpoint", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, "https://display.bfmio.com/prebid_display") + cmpStrings(t, "adapters.beachfront.extra_info", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo, "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") cmpStrings(t, "adapters.ix.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint, "http://ixtest.com/api") cmpStrings(t, "adapters.rubicon.endpoint", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, "http://rubitest.com/api") cmpStrings(t, "adapters.rubicon.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRubicon)].UserSyncURL, "http://pixel.rubiconproject.com/sync.php?p=prebid") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 8e779822cae..20805fb7898 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -85,15 +85,14 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdvangelists: advangelists.NewAdvangelistsBidder(cfg.Adapters[string(openrtb_ext.BidderAdvangelists)].Endpoint), openrtb_ext.BidderApplogy: applogy.NewApplogyBidder(cfg.Adapters[string(openrtb_ext.BidderApplogy)].Endpoint), openrtb_ext.BidderAppnexus: appnexus.NewAppNexusBidder(client, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - // TODO #615: Update the config setup so that the Beachfront URLs can be configured, and use those in TestRaceIntegration in exchange_test.go - openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(), - openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), - openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), - openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), - openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), - openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), - openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), - openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), + openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo), + openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), + openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), + openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), + openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), + openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), + openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), + openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index f263eea8569..e7df5e85733 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -483,6 +483,11 @@ func TestRaceIntegration(t *testing.T) { Endpoint: server.URL, PlatformID: "abc", } + cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderBeachfront))] = config.Adapter{ + Endpoint: server.URL, + ExtraAdapterInfo: "{\"video_endpoint\":\"" + server.URL + "\"}", + } + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) From 76747ef3b2cb0cce51c624b2f3cb8161021952ab Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Thu, 16 Apr 2020 10:35:27 -0400 Subject: [PATCH 054/603] Add nil check errors when setting native asset types (#1260) --- exchange/bidder.go | 39 ++++++++--- exchange/bidder_test.go | 144 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 11 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 8e95835ffba..7a53db5ee97 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -224,26 +224,43 @@ func addNativeTypes(bid *openrtb.Bid, request *openrtb.BidRequest) (*nativeRespo } for _, asset := range nativeMarkup.Assets { - setAssetTypes(asset, nativePayload) + if err := setAssetTypes(asset, nativePayload); err != nil { + errs = append(errs, err) + } } return nativeMarkup, errs } -func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) { +func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { if asset.Img != nil { - tempAsset := getAssetByID(asset.ID, nativePayload.Assets) - if tempAsset.Img.Type != 0 { - asset.Img.Type = tempAsset.Img.Type + if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if tempAsset.Img != nil { + if tempAsset.Img.Type != 0 { + asset.Img.Type = tempAsset.Img.Type + } + } else { + return fmt.Errorf("Response has an Image asset with ID:%d present that doesn't exist in the request", asset.ID) + } + } else { + return err } } if asset.Data != nil { - tempAsset := getAssetByID(asset.ID, nativePayload.Assets) - if tempAsset.Data.Type != 0 { - asset.Data.Type = tempAsset.Data.Type + if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if tempAsset.Data != nil { + if tempAsset.Data.Type != 0 { + asset.Data.Type = tempAsset.Data.Type + } + } else { + return fmt.Errorf("Response has a Data asset with ID:%d present that doesn't exist in the request", asset.ID) + } + } else { + return err } } + return nil } func getNativeImpByImpID(impID string, request *openrtb.BidRequest) (*openrtb.Native, error) { @@ -255,13 +272,13 @@ func getNativeImpByImpID(impID string, request *openrtb.BidRequest) (*openrtb.Na return nil, errors.New("Could not find native imp") } -func getAssetByID(id int64, assets []nativeRequests.Asset) nativeRequests.Asset { +func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset, error) { for _, asset := range assets { if id == asset.ID { - return asset + return asset, nil } } - return nativeRequests.Asset{} + return nativeRequests.Asset{}, fmt.Errorf("Unable to find asset with ID:%d in the request", id) } // makeExt transforms information about the HTTP call into the contract class for the PBS response. diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 46f63cc66c4..f20b431c13a 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -15,6 +15,9 @@ import ( "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" + + nativeRequests "github.com/mxmCherry/openrtb/native/request" + nativeResponse "github.com/mxmCherry/openrtb/native/response" ) // TestSingleBidder makes sure that the following things work if the Bidder needs only one request. @@ -1083,6 +1086,147 @@ func TestErrorReporting(t *testing.T) { } } +func TestSetAssetTypes(t *testing.T) { + testCases := []struct { + respAsset nativeResponse.Asset + nativeReq nativeRequests.Request + expectedErr string + desc string + }{ + { + respAsset: nativeResponse.Asset{ + ID: 1, + Img: &nativeResponse.Image{ + URL: "http://some-url", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + { + ID: 2, + Data: &nativeRequests.Data{ + Type: 4, + }, + }, + }, + }, + expectedErr: "", + desc: "Matching image asset exists in the request and asset type is set correctly", + }, + { + respAsset: nativeResponse.Asset{ + ID: 2, + Data: &nativeResponse.Data{ + Label: "some label", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + { + ID: 2, + Data: &nativeRequests.Data{ + Type: 4, + }, + }, + }, + }, + expectedErr: "", + desc: "Matching data asset exists in the request and asset type is set correctly", + }, + { + respAsset: nativeResponse.Asset{ + ID: 1, + Img: &nativeResponse.Image{ + URL: "http://some-url", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 2, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Unable to find asset with ID:1 in the request", + desc: "Matching image asset with the same ID doesn't exist in the request", + }, + { + respAsset: nativeResponse.Asset{ + ID: 2, + Data: &nativeResponse.Data{ + Label: "some label", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 2, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response has a Data asset with ID:2 present that doesn't exist in the request", + desc: "Assets with same ID in the req and resp are of different types", + }, + { + respAsset: nativeResponse.Asset{ + ID: 1, + Img: &nativeResponse.Image{ + URL: "http://some-url", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Data: &nativeRequests.Data{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response has an Image asset with ID:1 present that doesn't exist in the request", + desc: "Assets with same ID in the req and resp are of different types", + }, + } + + for _, test := range testCases { + err := setAssetTypes(test.respAsset, test.nativeReq) + if len(test.expectedErr) != 0 { + assert.EqualError(t, err, test.expectedErr, "Test Case: %s", test.desc) + continue + } else { + assert.NoError(t, err, "Test Case: %s", test.desc) + } + + for _, asset := range test.nativeReq.Assets { + if asset.Img != nil && test.respAsset.Img != nil { + assert.Equal(t, asset.Img.Type, test.respAsset.Img.Type, "Asset type not set correctly. Test Case: %s", test.desc) + } + if asset.Data != nil && test.respAsset.Data != nil { + assert.Equal(t, asset.Data.Type, test.respAsset.Data.Type, "Asset type not set correctly. Test Case: %s", test.desc) + } + } + } +} + type goodSingleBidder struct { bidRequest *openrtb.BidRequest httpRequest *adapters.RequestData From 79bb4dc745ae2220fe8baae04fa12a4c12b1827c Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 16 Apr 2020 07:55:47 -0700 Subject: [PATCH 055/603] Bugfix: no bids from bidder handling (#1252) Co-authored-by: Veronika Solovei --- exchange/exchange.go | 30 ++- exchange/exchange_test.go | 19 +- .../request-multi-bidders-debug-info.json | 227 ++++++++++++++++++ .../request-multi-bidders-one-no-resp.json | 122 ++++++++++ 4 files changed, 386 insertions(+), 12 deletions(-) create mode 100644 exchange/exchangetest/request-multi-bidders-debug-info.json create mode 100644 exchange/exchangetest/request-multi-bidders-one-no-resp.json diff --git a/exchange/exchange.go b/exchange/exchange.go index e625e5ca8f3..48903dd98c7 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -63,6 +63,9 @@ type exchange struct { type seatResponseExtra struct { ResponseTimeMillis int Errors []openrtb_ext.ExtBidderError + // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. + // This will become response.ext.debug.httpcalls.{bidder} on the final Response. + HttpCalls []*openrtb_ext.ExtHttpCall } type bidResponseWrapper struct { @@ -324,6 +327,10 @@ func (e *exchange) getAllBids(ctx context.Context, cleanRequests map[openrtb_ext // Structure to record extra tracking data generated during bidding ae := new(seatResponseExtra) ae.ResponseTimeMillis = int(elapsed / time.Millisecond) + if bids != nil { + ae.HttpCalls = bids.httpCalls + } + // Timing statistics e.me.RecordAdapterTime(*bidlabels, time.Since(start)) serr := errsToBidderErrors(err) @@ -346,7 +353,12 @@ func (e *exchange) getAllBids(ctx context.Context, cleanRequests map[openrtb_ext // Wait for the bidders to do their thing for i := 0; i < len(cleanRequests); i++ { brw := <-chBids - adapterBids[brw.bidder] = brw.adapterBids + + //if bidder returned no bids back - remove bidder from further processing + if brw.adapterBids != nil && len(brw.adapterBids.bids) != 0 { + adapterBids[brw.bidder] = brw.adapterBids + } + //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].bids) > 0 { @@ -639,20 +651,20 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb } } - for a, b := range adapterBids { - if b != nil && req.Test == 1 { - // Fill debug info - bidResponseExt.Debug.HttpCalls[a] = b.httpCalls + for bidderName, responseExtra := range adapterExtra { + + if req.Test == 1 { + bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } // Only make an entry for bidder errors if the bidder reported any. - if len(adapterExtra[a].Errors) > 0 { - bidResponseExt.Errors[a] = adapterExtra[a].Errors + if len(responseExtra.Errors) > 0 { + bidResponseExt.Errors[bidderName] = responseExtra.Errors } if len(errList) > 0 { bidResponseExt.Errors[openrtb_ext.PrebidExtKey] = errsToBidderErrors(errList) } - bidResponseExt.ResponseTimeMillis[a] = adapterExtra[a].ResponseTimeMillis - // Defering the filling of bidResponseExt.Usersync[a] until later + bidResponseExt.ResponseTimeMillis[bidderName] = responseExtra.ResponseTimeMillis + // Defering the filling of bidResponseExt.Usersync[bidderName] until later } return bidResponseExt diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index e7df5e85733..3c1c2f3bc72 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -771,6 +771,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } } } + if spec.IncomingRequest.OrtbRequest.Test == 1 { + //compare debug info + diffJson(t, "Debug info modified", bid.Ext, spec.Response.Ext) + + } } func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) []string { @@ -1625,6 +1630,7 @@ type exchangeRequest struct { type exchangeResponse struct { Bids *openrtb.BidResponse `json:"bids"` Error string `json:"error,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } type bidderSpec struct { @@ -1638,8 +1644,9 @@ type bidderRequest struct { } type bidderResponse struct { - SeatBid *bidderSeatBid `json:"pbsSeatBid,omitempty"` - Errors []string `json:"errors,omitempty"` + SeatBid *bidderSeatBid `json:"pbsSeatBid,omitempty"` + Errors []string `json:"errors,omitempty"` + HttpCalls []*openrtb_ext.ExtHttpCall `json:"httpCalls,omitempty"` } // bidderSeatBid is basically a subset of pbsOrtbSeatBid from exchange/bidder.go. @@ -1696,7 +1703,13 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidR } seatBid = &pbsOrtbSeatBid{ - bids: bids, + bids: bids, + httpCalls: mockResponse.HttpCalls, + } + } else { + seatBid = &pbsOrtbSeatBid{ + bids: nil, + httpCalls: mockResponse.HttpCalls, } } diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json new file mode 100644 index 00000000000..ec174f75b36 --- /dev/null +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -0,0 +1,227 @@ +{ + "incomingRequest": { + "ortbRequest": { + "test": 1, + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "durationRangeSec": [ + 15, + 30 + ], + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withCategory": true, + "translateCategories": true + } + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "httpCalls": [ + { + "uri": "appnexusTest.com", + "requestbody": "appnexusTestRequestBody", + "responsebody": "appnexusTestResponseBody", + "status": 200 + } + ], + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + } + } + ] + } + } + }, + "audienceNetwork": { + "mockResponse": { + "httpCalls": [ + { + "uri": "audienceNetworkTest.com", + "requestbody": "audienceNetworkTestRequestBody", + "responsebody": "audienceNetworkTestResponseBody", + "status": 200 + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "prebid": { + "type": "", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_size": "200x250", + "hb_size_appnexus": "200x250", + "hb_pb": "12.00", + "hb_pb_appnexus": "12.00", + "hb_pb_cat_dur": "12.00_VideoGames_15s", + "hb_pb_cat_dur_appnex": "12.00_VideoGames_15s" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "httpcalls": { + "appnexus": [ + { + "uri": "appnexusTest.com", + "requestbody": "appnexusTestRequestBody", + "responsebody": "appnexusTestResponseBody", + "status": 200 + } + ], + "audienceNetwork": [ + { + "uri": "audienceNetworkTest.com", + "requestbody": "audienceNetworkTestRequestBody", + "responsebody": "audienceNetworkTestResponseBody", + "status": 200 + } + ] + }, + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "durationRangeSec": [ + 15, + 30 + ], + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withCategory": true, + "translateCategories": true + } + } + } + } + } + } + } + } +} + + diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json new file mode 100644 index 00000000000..b7179ccb02e --- /dev/null +++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json @@ -0,0 +1,122 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "durationRangeSec": [15,30], + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withCategory": true, + "translateCategories": true + } + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + } + } + ] + } + } + }, + "audienceNetwork": { + "mockResponse": { + + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 12.00, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": ["IAB1-1"], + "ext": { + "prebid": { + "type": "", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_size": "200x250", + "hb_size_appnexus": "200x250", + "hb_pb": "12.00", + "hb_pb_appnexus": "12.00", + "hb_pb_cat_dur": "12.00_VideoGames_15s", + "hb_pb_cat_dur_appnex": "12.00_VideoGames_15s" + } + } + } + }] + } + ] + } + } +} + + From 8657ae9b0113855e7dd15f921b3cf6f0387a63c3 Mon Sep 17 00:00:00 2001 From: jmaynardxandr <46759873+jmaynardxandr@users.noreply.github.com> Date: Tue, 21 Apr 2020 12:17:38 -0700 Subject: [PATCH 056/603] Add missing categories to AppNexus -> IAB mapping file. (#1264) * Add missing categories to AppNexus -> IAB mapping file. * Remove entry for category 38 which was set to a primary IAB category instead of a sub-category. * Fix order of category 22 --- static/adapter/appnexus/opts.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/adapter/appnexus/opts.json b/static/adapter/appnexus/opts.json index bd6f8af3e8b..3ea849735de 100644 --- a/static/adapter/appnexus/opts.json +++ b/static/adapter/appnexus/opts.json @@ -20,8 +20,11 @@ "19": "IAB18-4", "20": "IAB1-5", "21": "IAB1-6", + "22": "IAB19-28", "23": "IAB19-13", "24": "IAB22-2", + "25": "IAB3-9", + "26": "IAB17-26", "27": "IAB19-6", "28": "IAB1-7", "29": "IAB9-5", @@ -31,7 +34,6 @@ "33": "IAB16-5", "34": "IAB19-34", "37": "IAB11-4", - "38": "IAB23", "39": "IAB9-30", "41": "IAB7-44", "51": "IAB17-12", From dc9335ca0a343d4ccc89def2307562a6576c86df Mon Sep 17 00:00:00 2001 From: hbanalytics <55453525+hbanalytics@users.noreply.github.com> Date: Wed, 22 Apr 2020 17:11:38 +0300 Subject: [PATCH 057/603] Yieldone s2s Bid Adapter (#1242) * Added new Yieldone Bid s2s Adapter * Update endpoint for yieldone bid adapter * Fixes after review for Yieldone Bid s2s Adapter * Fix typeo in Yieldone s2s Bid Adapter --- adapters/yieldone/params_test.go | 48 ++++++ adapters/yieldone/usersync.go | 12 ++ adapters/yieldone/usersync_test.go | 30 ++++ adapters/yieldone/yieldone.go | 144 ++++++++++++++++++ adapters/yieldone/yieldone_test.go | 11 ++ .../yieldonetest/exemplary/simple-banner.json | 89 +++++++++++ .../yieldonetest/exemplary/simple-video.json | 87 +++++++++++ .../yieldonetest/params/race/banner.json | 4 + .../supplemental/bad_response.json | 65 ++++++++ .../yieldonetest/supplemental/status_204.json | 60 ++++++++ .../yieldonetest/supplemental/status_400.json | 65 ++++++++ .../yieldonetest/supplemental/status_418.json | 65 ++++++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_yieldone.go | 6 + static/bidder-info/yieldone.yaml | 11 ++ static/bidder-params/yieldone.json | 15 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 20 files changed, 721 insertions(+) create mode 100644 adapters/yieldone/params_test.go create mode 100644 adapters/yieldone/usersync.go create mode 100644 adapters/yieldone/usersync_test.go create mode 100644 adapters/yieldone/yieldone.go create mode 100644 adapters/yieldone/yieldone_test.go create mode 100644 adapters/yieldone/yieldonetest/exemplary/simple-banner.json create mode 100644 adapters/yieldone/yieldonetest/exemplary/simple-video.json create mode 100644 adapters/yieldone/yieldonetest/params/race/banner.json create mode 100644 adapters/yieldone/yieldonetest/supplemental/bad_response.json create mode 100644 adapters/yieldone/yieldonetest/supplemental/status_204.json create mode 100644 adapters/yieldone/yieldonetest/supplemental/status_400.json create mode 100644 adapters/yieldone/yieldonetest/supplemental/status_418.json create mode 100644 openrtb_ext/imp_yieldone.go create mode 100644 static/bidder-info/yieldone.yaml create mode 100644 static/bidder-params/yieldone.json diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go new file mode 100644 index 00000000000..6048ea5d7dc --- /dev/null +++ b/adapters/yieldone/params_test.go @@ -0,0 +1,48 @@ +package yieldone + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderYieldone, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Yieldone params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderYieldone, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": "123"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `[]`, + `true`, + `2`, + `{"invalid_param": "123"}`, + `{"placementId": 123}`, +} diff --git a/adapters/yieldone/usersync.go b/adapters/yieldone/usersync.go new file mode 100644 index 00000000000..bc9d1b3235b --- /dev/null +++ b/adapters/yieldone/usersync.go @@ -0,0 +1,12 @@ +package yieldone + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewYieldoneSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("yieldone", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/yieldone/usersync_test.go b/adapters/yieldone/usersync_test.go new file mode 100644 index 00000000000..902f3b66b34 --- /dev/null +++ b/adapters/yieldone/usersync_test.go @@ -0,0 +1,30 @@ +package yieldone + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestYieldoneSyncer(t *testing.T) { + syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewYieldoneSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go new file mode 100644 index 00000000000..f02d1a0b088 --- /dev/null +++ b/adapters/yieldone/yieldone.go @@ -0,0 +1,144 @@ +package yieldone + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type YieldoneAdapter struct { + endpoint string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors = make([]error, 0) + + var validImps []openrtb.Imp + for i := 0; i < len(request.Imp); i++ { + if err := preprocess(&request.Imp[i]); err == nil { + validImps = append(validImps, request.Imp[i]) + } else { + errors = append(errors, err) + } + } + + request.Imp = validImps + + reqJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }}, errors +} + +// MakeBids unpacks the server's response into Bids. +func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + } + return bidResponse, nil + +} + +// NewYieldoneBidder configure bidder endpoint +func NewYieldoneBidder(endpoint string) *YieldoneAdapter { + return &YieldoneAdapter{ + endpoint: endpoint, + } +} + +func preprocess(imp *openrtb.Imp) error { + + var ext adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return err + } + var impressionExt openrtb_ext.ExtImpYieldone + if err := json.Unmarshal(ext.Bidder, &impressionExt); err != nil { + return err + } + + if imp.Banner != nil { + bannerCopy := *imp.Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + firstFormat := bannerCopy.Format[0] + bannerCopy.W = &(firstFormat.W) + bannerCopy.H = &(firstFormat.H) + } + imp.Banner = &bannerCopy + } + + return nil +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go new file mode 100644 index 00000000000..34d58bafbd7 --- /dev/null +++ b/adapters/yieldone/yieldone_test.go @@ -0,0 +1,11 @@ +package yieldone + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "yieldonetest", NewYieldoneBidder("http://localhost/prebid")) +} diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-banner.json b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..f84476f1e86 --- /dev/null +++ b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "yieldone", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "JPY" + } + } + }], + + "expectedBidResponses": [{ + "currency": "JPY", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-video.json b/adapters/yieldone/yieldonetest/exemplary/simple-video.json new file mode 100644 index 00000000000..dc313abede7 --- /dev/null +++ b/adapters/yieldone/yieldonetest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "41993" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "41993" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "yieldone", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad-vast", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "JPY" + } + } + }], + + "expectedBidResponses": [{ + "currency": "JPY", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad-vast", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "video" + }] + }] +} diff --git a/adapters/yieldone/yieldonetest/params/race/banner.json b/adapters/yieldone/yieldonetest/params/race/banner.json new file mode 100644 index 00000000000..c88180845eb --- /dev/null +++ b/adapters/yieldone/yieldonetest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "placementId": "36891" +} + diff --git a/adapters/yieldone/yieldonetest/supplemental/bad_response.json b/adapters/yieldone/yieldonetest/supplemental/bad_response.json new file mode 100644 index 00000000000..fa993a2fff5 --- /dev/null +++ b/adapters/yieldone/yieldonetest/supplemental/bad_response.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/yieldone/yieldonetest/supplemental/status_204.json b/adapters/yieldone/yieldonetest/supplemental/status_204.json new file mode 100644 index 00000000000..b1c9304a35a --- /dev/null +++ b/adapters/yieldone/yieldonetest/supplemental/status_204.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/yieldone/yieldonetest/supplemental/status_400.json b/adapters/yieldone/yieldonetest/supplemental/status_400.json new file mode 100644 index 00000000000..1cb172bb371 --- /dev/null +++ b/adapters/yieldone/yieldonetest/supplemental/status_400.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/yieldone/yieldonetest/supplemental/status_418.json b/adapters/yieldone/yieldonetest/supplemental/status_418.json new file mode 100644 index 00000000000..30cc16adde5 --- /dev/null +++ b/adapters/yieldone/yieldonetest/supplemental/status_418.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "36891" + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 1c686591ef2..c944153a6b0 100644 --- a/config/config.go +++ b/config/config.go @@ -544,6 +544,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_sc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") } @@ -742,6 +743,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") + v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 20805fb7898..585f89beb19 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -64,6 +64,7 @@ import ( "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yieldmo" + "github.com/prebid/prebid-server/adapters/yieldone" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -137,6 +138,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), openrtb_ext.BidderYieldmo: yieldmo.NewYieldmoBidder(cfg.Adapters[string(openrtb_ext.BidderYieldmo)].Endpoint), + openrtb_ext.BidderYieldone: yieldone.NewYieldoneBidder(cfg.Adapters[string(openrtb_ext.BidderYieldone)].Endpoint), openrtb_ext.BidderZeroClickFraud: zeroclickfraud.NewZeroClickFraudBidder(cfg.Adapters[string(openrtb_ext.BidderZeroClickFraud)].Endpoint), } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 43d9b894ea8..508379b5df9 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -82,6 +82,7 @@ const ( BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYieldmo BidderName = "yieldmo" + BidderYieldone BidderName = "yieldone" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -146,6 +147,7 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "vrtcal": BidderVrtcal, "yieldmo": BidderYieldmo, + "yieldone": BidderYieldone, "zeroclickfraud": BidderZeroClickFraud, } diff --git a/openrtb_ext/imp_yieldone.go b/openrtb_ext/imp_yieldone.go new file mode 100644 index 00000000000..6eee563b448 --- /dev/null +++ b/openrtb_ext/imp_yieldone.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpYieldone defines the contract for bidrequest.imp[i].ext.yieldone +type ExtImpYieldone struct { + PlacementId string `json:"placementId"` +} diff --git a/static/bidder-info/yieldone.yaml b/static/bidder-info/yieldone.yaml new file mode 100644 index 00000000000..74aef46d24f --- /dev/null +++ b/static/bidder-info/yieldone.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "y1dev@platform-one.co.jp" +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/yieldone.json b/static/bidder-params/yieldone.json new file mode 100644 index 00000000000..15d7acec177 --- /dev/null +++ b/static/bidder-params/yieldone.json @@ -0,0 +1,15 @@ + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Yieldone Adapter Params", + "description": "A schema which validates params accepted by the Yieldone adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "string", + "description": "Internal Yieldone Placement ID" + } + }, + "required": ["placementId"] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index da235402bf0..995f573adca 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -57,6 +57,7 @@ import ( "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yieldmo" + "github.com/prebid/prebid-server/adapters/yieldone" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -121,6 +122,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) return syncers diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 637f590e25f..c25a91bc6b4 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -66,6 +66,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, string(openrtb_ext.BidderYieldmo): syncConfig, + string(openrtb_ext.BidderYieldone): syncConfig, string(openrtb_ext.BidderZeroClickFraud): syncConfig, }, } From b44980c8e53af42881ba7160b606c641c1730059 Mon Sep 17 00:00:00 2001 From: chino117 Date: Wed, 22 Apr 2020 11:15:24 -0300 Subject: [PATCH 058/603] Fix: URL de sync (#1261) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index c944153a6b0..22c5b96222c 100644 --- a/config/config.go +++ b/config/config.go @@ -509,7 +509,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3F"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderFacebook doesn't have a good default. // openrtb_ext.BidderGamma doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGamoshi, "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bgusr%5D") From 99dc46bc7eef9bf50b45ce34ad275b02425c0784 Mon Sep 17 00:00:00 2001 From: Aadesh Date: Wed, 22 Apr 2020 10:29:25 -0400 Subject: [PATCH 059/603] populate the app ID in the FAN timeout notif url with the publisher ID (#1265) and the auction with the request ID Co-authored-by: Aadesh Patel --- .../audienceNetworktest/exemplary/banner.json | 6 ++-- .../exemplary/interstitial.json | 6 ++-- .../exemplary/native-1.1.json | 6 ++-- .../audienceNetworktest/exemplary/video.json | 6 ++-- .../supplemental/banner-format-only.json | 6 ++-- .../supplemental/multi-imp.json | 12 +++---- .../supplemental/no-bid-204.json | 4 +-- .../supplemental/split-placementId.json | 6 ++-- adapters/audienceNetwork/facebook.go | 32 +++++++++++++++++-- adapters/audienceNetwork/facebook_test.go | 22 +++++++++++-- 10 files changed, 74 insertions(+), 32 deletions(-) diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json index 632629b53a2..f5f92515e26 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json @@ -51,7 +51,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -84,7 +84,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -92,7 +92,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json index 630e26d3f90..bad228d5f18 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json @@ -52,7 +52,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -86,7 +86,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -94,7 +94,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json index 288c7c14e5d..9090d80d099 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json @@ -45,7 +45,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -78,7 +78,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -86,7 +86,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json index 15563c2ada5..22c62f8b821 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json @@ -50,7 +50,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -88,7 +88,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -96,7 +96,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json index 52b7655593a..3edd6569258 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json @@ -53,7 +53,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -86,7 +86,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -94,7 +94,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json index 0fe836af4de..16e8aede10c 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json @@ -70,7 +70,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-1", "imp": [ { "id": "test-imp-1", @@ -103,7 +103,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "dfecd103a45daeb2a01728afb8ce78f6738f6007ecfebe1ca616b196e22b43e9", "platformid": "test-platform-id" } } @@ -111,7 +111,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-1", "seatbid": [ { "bid": [ @@ -147,7 +147,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-2", "imp": [ { "id": "test-imp-2", @@ -180,7 +180,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "a5fead11a4db86d0f62f57c3d8001640227120c8ef236549f0db010c1dbab399", "platformid": "test-platform-id" } } @@ -188,7 +188,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-2", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json index 042c86bd7fd..bb192aad76f 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json @@ -45,7 +45,7 @@ ] }, "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -78,7 +78,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json index b99834ab1df..4c561c55276 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json @@ -39,7 +39,7 @@ "expectedRequest": { "uri": "https://an.facebook.com/placementbid.ortb", "body": { - "id": "test-req-id", + "id": "test-imp-id", "imp": [ { "id": "test-imp-id", @@ -72,7 +72,7 @@ }, "tmax": 500, "ext": { - "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e", + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", "platformid": "test-platform-id" } } @@ -80,7 +80,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-req-id", + "id": "test-imp-id", "seatbid": [ { "bid": [ diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index db7657f59b7..9edb9a7d57e 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -130,6 +130,11 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { return err } + // Every outgoing FAN request has a single impression, so we can safely use the unique + // impression ID as the FAN request ID. We need to make sure that we update the request + // ID *BEFORE* we generate the auth ID since its a hash based on the request ID + out.ID = imp.ID + reqExt := facebookReqExt{ PlatformID: this.platformID, AuthID: this.makeAuthID(out), @@ -455,18 +460,39 @@ func NewFacebookBidder(client *http.Client, platformID string, appSecret string) } func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { - // Note, facebook creates one request per imp, so all these requests will only have one imp in them - auction_id, err := jsonparser.GetString(req.Body, "imp", "[0]", "id") + var ( + rID string + pubID string + err error + ) + + // Note, the facebook adserver can only handle single impression requests, so we have to split multi-imp requests into + // multiple request. In order to ensure that every split request has a unique ID, the split request IDs are set to the + // corresponding imp's ID + rID, err = jsonparser.GetString(req.Body, "id") if err != nil { return &adapters.RequestData{}, []error{err} } - uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, fa.platformID, auction_id) + // The publisher ID is either in the app object or the site object, depending on the supply of the request so we need + // to check both + pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id") + if err != nil { + pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id") + if err != nil { + return &adapters.RequestData{}, []error{ + errors.New("path [app|site].publisher.id not found in the request"), + } + } + } + + uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, pubID, rID) timeoutReq := adapters.RequestData{ Method: "GET", Uri: uri, Body: nil, Headers: http.Header{}, } + return &timeoutReq, nil } diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 1edaabd45d7..784a540e596 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -43,9 +43,9 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder(nil, "test-platform-id", "test-app-secret")) } -func TestMakeTimeoutNotice(t *testing.T) { +func TestMakeTimeoutNoticeApp(t *testing.T) { req := adapters.RequestData{ - Body: []byte(`{"imp":[{"id":"1234"}]}}`), + Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"app":{"publisher":{"id":"5678"}}}`), } fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") @@ -56,9 +56,25 @@ func TestMakeTimeoutNotice(t *testing.T) { toReq, err := tb.MakeTimeoutNotification(&req) assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err) - expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=test-platform-id&auction=1234&ortb_loss_code=2" + expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2" assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.") +} +func TestMakeTimeoutNoticeSite(t *testing.T) { + req := adapters.RequestData{ + Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`), + } + fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + + tb, ok := fba.(adapters.TimeoutBidder) + if !ok { + t.Error("Facebook adapter is not a TimeoutAdapter") + } + + toReq, err := tb.MakeTimeoutNotification(&req) + assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err) + expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2" + assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.") } func TestMakeTimeoutNoticeBadRequest(t *testing.T) { From f4905e8fa74a6681091751d15c9c0e9de0252133 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Wed, 22 Apr 2020 07:30:58 -0700 Subject: [PATCH 060/603] Added header User Agent decoding (#1268) * Added header User Agent decoding * Added header User Agent decoding: unit tests * Added header User Agent decoding: unit tests * Added check UA is encoded to avoid `+` converted to space Co-authored-by: Veronika Solovei --- endpoints/openrtb2/video_auction.go | 16 +++++++-- endpoints/openrtb2/video_auction_test.go | 43 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 0215eb4cff2..c7316604d73 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -8,12 +8,13 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "strconv" "strings" "time" "github.com/buger/jsonparser" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/prebid/prebid-server/errortypes" @@ -617,7 +618,18 @@ func (deps *endpointDeps) parseVideoRequest(request []byte, headers http.Header) //if Device.UA is not present in request body, init it with user-agent from request header if it's present if req.Device.UA == "" { - req.Device.UA = headers.Get("User-Agent") + ua := headers.Get("User-Agent") + + //Check UA is encoded. Without it the `+` character would get changed to a space if not actually encoded + if strings.ContainsAny(ua, "%") { + var err error + req.Device.UA, err = url.QueryUnescape(ua) + if err != nil { + req.Device.UA = ua + } + } else { + req.Device.UA = ua + } } errL, podErrors := deps.validateVideoRequest(req) diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index d0ce33de1c4..ec525c6ff08 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -927,6 +927,49 @@ func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) { } +func TestParseVideoRequestWithEncodedUserAgentInHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_without_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + uaEncoded := "Mozilla%2F5.0%20%28Macintosh%3B%20Intel%20Mac%20OS%20X%2010_14_6%29%20AppleWebKit%2F537.36%20%28KHTML%2C%20like%20Gecko%29%20Chrome%2F78.0.3904.87%20Safari%2F537.36" + uaDecoded := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" + + headers := http.Header{} + headers.Add("User-Agent", uaEncoded) + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + +func TestParseVideoRequestWithDecodedUserAgentInHeader(t *testing.T) { + ex := &mockExchangeVideo{} + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample_without_device_user_agent.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + uaDecoded := "Mozilla/5.0+(Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" + + headers := http.Header{} + headers.Add("User-Agent", uaDecoded) + + deps := mockDeps(t, ex) + req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + + assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header") + assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") + assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned") + +} + func TestHandleErrorDebugLog(t *testing.T) { vo := analytics.VideoObject{ Status: 200, From 62a135779b88ada41ac9872c2455a8d092fb02c4 Mon Sep 17 00:00:00 2001 From: Ad Generation Date: Fri, 24 Apr 2020 00:45:45 +0900 Subject: [PATCH 061/603] Ad Generation Adapter Integration. (#1253) * AdGeneration Integration. * update AdGeneration adapter. fix: some methods of the adgAdapter replace to functions. fix: unmarshal functions return a pointer. fix: header is defined once. fix: return when imps is appended * update AdGeneration Adapter. add: Added a comment in usersync. add: Added a test for parameters whose ID does not exist in params_test. change: Change to query creation by net/url. Added getRawQuery Test. fix: Changed variable names related to bidRequest. --- adapters/adgeneration/adgeneration.go | 260 ++++++++++++++++++ adapters/adgeneration/adgeneration_test.go | 176 ++++++++++++ .../exemplary/single-banner.json | 151 ++++++++++ .../adgenerationtest/params/race/banner.json | 3 + .../supplemental/204-bid-response.json | 72 +++++ .../supplemental/400-bid-response.json | 77 ++++++ .../supplemental/invalid-adg-param.json | 31 +++ .../supplemental/no-bid-response.json | 89 ++++++ adapters/adgeneration/params_test.go | 47 ++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adgeneration.go | 5 + static/bidder-info/adgeneration.yaml | 10 + static/bidder-params/adgeneration.json | 15 + usersync/usersyncers/syncer_test.go | 13 +- 16 files changed, 949 insertions(+), 6 deletions(-) create mode 100644 adapters/adgeneration/adgeneration.go create mode 100644 adapters/adgeneration/adgeneration_test.go create mode 100644 adapters/adgeneration/adgenerationtest/exemplary/single-banner.json create mode 100644 adapters/adgeneration/adgenerationtest/params/race/banner.json create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/invalid-adg-param.json create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json create mode 100644 adapters/adgeneration/params_test.go create mode 100644 openrtb_ext/imp_adgeneration.go create mode 100644 static/bidder-info/adgeneration.yaml create mode 100644 static/bidder-params/adgeneration.json diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go new file mode 100644 index 00000000000..4b1215dea9d --- /dev/null +++ b/adapters/adgeneration/adgeneration.go @@ -0,0 +1,260 @@ +package adgeneration + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type AdgenerationAdapter struct { + endpoint string + version string + defaultCurrency string +} + +// Server Responses +type adgServerResponse struct { + Locationid string `json:"locationid"` + Dealid string `json:"dealid"` + Ad string `json:"ad"` + Beacon string `json:"beacon"` + Beaconurl string `json:"beaconurl"` + Cpm float64 `jsons:"cpm"` + Creativeid string `json:"creativeid"` + H uint64 `json:"h"` + W uint64 `json:"w"` + Ttl uint64 `json:"ttl"` + Vastxml string `json:"vastxml,omitempty"` + LandingUrl string `json:"landing_url"` + Scheduleid string `json:"scheduleid"` + Results []interface{} `json:"results"` +} + +func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + numRequests := len(request.Imp) + var errs []error + + if numRequests == 0 { + errs = append(errs, &errortypes.BadInput{ + Message: "No impression in the bid request", + }) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + bidRequestArray := make([]*adapters.RequestData, 0, numRequests) + + for index := 0; index < numRequests; index++ { + bidRequestUri, err := adg.getRequestUri(request, index) + if err != nil { + errs = append(errs, err) + return nil, errs + } + bidRequest := &adapters.RequestData{ + Method: "GET", + Uri: bidRequestUri, + Body: nil, + Headers: headers, + } + bidRequestArray = append(bidRequestArray, bidRequest) + } + + return bidRequestArray, errs +} + +func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index int) (string, error) { + imp := request.Imp[index] + adgExt, err := unmarshalExtImpAdgeneration(&imp) + if err != nil { + return "", &errortypes.BadInput{ + Message: err.Error(), + } + } + uriObj, err := url.Parse(adg.endpoint) + if err != nil { + return "", &errortypes.BadInput{ + Message: err.Error(), + } + } + v := adg.getRawQuery(adgExt.Id, request, &imp) + uriObj.RawQuery = v.Encode() + return uriObj.String(), err +} + +func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidRequest, imp *openrtb.Imp) *url.Values { + v := url.Values{} + v.Set("posall", "SSPLOC") + v.Set("id", id) + v.Set("sdktype", "0") + v.Set("hb", "true") + v.Set("t", "json3") + v.Set("currency", adg.getCurrency(request)) + v.Set("sdkname", "prebidserver") + v.Set("adapterver", adg.version) + adSize := getSizes(imp) + if adSize != "" { + v.Set("size", adSize) + } + if request.Site != nil && request.Site.Page != "" { + v.Set("tp", request.Site.Page) + } + return &v +} + +func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { + var bidderExt adapters.ExtImpBidder + var adgExt openrtb_ext.ExtImpAdgeneration + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, err + } + if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil { + return nil, err + } + if adgExt.Id == "" { + return nil, errors.New("No Location ID in ExtImpAdgeneration.") + } + return &adgExt, nil +} + +func getSizes(imp *openrtb.Imp) string { + if imp.Banner == nil || len(imp.Banner.Format) == 0 { + return "" + } + var sizeStr string + for _, v := range imp.Banner.Format { + sizeStr += strconv.FormatUint(v.W, 10) + "×" + strconv.FormatUint(v.H, 10) + "," + } + if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 { + sizeStr = sizeStr[:len(sizeStr)-1] + } + return sizeStr +} + +func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string { + if len(request.Cur) <= 0 { + return adg.defaultCurrency + } else { + for _, c := range request.Cur { + if adg.defaultCurrency == c { + return c + } + } + return request.Cur[0] + } +} + +func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + var bidResp adgServerResponse + err := json.Unmarshal(response.Body, &bidResp) + if err != nil { + return nil, []error{err} + } + if len(bidResp.Results) <= 0 { + return nil, nil + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + var impId string + var bitType openrtb_ext.BidType + var adm string + for _, v := range internalRequest.Imp { + adgExt, err := unmarshalExtImpAdgeneration(&v) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }, + } + } + if adgExt.Id == bidResp.Locationid { + impId = v.ID + bitType = openrtb_ext.BidTypeBanner + adm = createAd(&bidResp, impId) + bid := openrtb.Bid{ + ID: bidResp.Locationid, + ImpID: impId, + AdM: adm, + Price: bidResp.Cpm, + W: bidResp.W, + H: bidResp.H, + CrID: bidResp.Creativeid, + DealID: bidResp.Dealid, + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bitType, + }) + return bidResponse, nil + } + } + return nil, nil +} + +func createAd(body *adgServerResponse, impId string) string { + ad := body.Ad + if body.Vastxml != "" { + ad = "
" + insertVASTMethod(impId, body.Vastxml) + "" + } + ad = appendChildToBody(ad, body.Beacon) + unwrappedAd := removeWrapper(ad) + if unwrappedAd != "" { + return unwrappedAd + } + return ad +} + +func insertVASTMethod(bidId string, vastxml string) string { + rep := regexp.MustCompile(`/\r?\n/g`) + var replacedVastxml = rep.ReplaceAllString(vastxml, "") + return "" +} + +func appendChildToBody(ad string, data string) string { + rep := regexp.MustCompile(`<\/\s?body>`) + return rep.ReplaceAllString(ad, data+"") +} + +func removeWrapper(ad string) string { + bodyIndex := strings.Index(ad, "") + lastBodyIndex := strings.LastIndex(ad, "") + if bodyIndex == -1 || lastBodyIndex == -1 { + return "" + } + + str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "", "", 1), "", "", 1)) + return str +} + +func NewAdgenerationAdapter(endpoint string) *AdgenerationAdapter { + return &AdgenerationAdapter{ + endpoint, + "1.0.0", + "JPY", + } +} diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go new file mode 100644 index 00000000000..e76995fc5e4 --- /dev/null +++ b/adapters/adgeneration/adgeneration_test.go @@ -0,0 +1,176 @@ +package adgeneration + +import ( + "encoding/json" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "adgenerationtest", NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")) +} + +func TestgetRequestUri(t *testing.T) { + bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1") + // Test items + failedRequest := &openrtb.BidRequest{ + ID: "test-failed-bid-request", + Imp: []openrtb.Imp{ + {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)}, + {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)}, + {ID: "extImpAdgeneration-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)}, + }, + Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb.Site{Page: "https://supership.com"}, + User: &openrtb.User{BuyerUID: "buyerID"}, + } + successRequest := &openrtb.BidRequest{ + ID: "test-success-bid-request", + Imp: []openrtb.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + }, + Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb.Site{Page: "https://supership.com"}, + User: &openrtb.User{BuyerUID: "buyerID"}, + } + + numRequests := len(failedRequest.Imp) + for index := 0; index < numRequests; index++ { + httpRequests, err := bidder.getRequestUri(failedRequest, index) + if err == nil { + t.Errorf("getRequestUri: %v did not throw an error", failedRequest.Imp[index]) + } + if httpRequests != "" { + t.Errorf("getRequestUri: %v did return Request: %s", failedRequest.Imp[index], httpRequests) + } + } + numRequests = len(successRequest.Imp) + for index := 0; index < numRequests; index++ { + // RequestUri Test. + httpRequests, err := bidder.getRequestUri(successRequest, index) + if err != nil { + t.Errorf("getRequestUri: %v did throw an error: %v", successRequest.Imp[index], err) + } + if httpRequests == "adapterver="+bidder.version+"¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html" { + t.Errorf("getRequestUri: %v did return Request: %s", successRequest.Imp[index], httpRequests) + } + // getRawQuery Test. + adgExt, err := unmarshalExtImpAdgeneration(&successRequest.Imp[index]) + if err != nil { + t.Errorf("unmarshalExtImpAdgeneration: %v did throw an error: %v", successRequest.Imp[index], err) + } + rawQuery := bidder.getRawQuery(adgExt.Id, successRequest, &successRequest.Imp[index]) + expectQueries := map[string]string{ + "posall": "SSPLOC", + "id": adgExt.Id, + "sdktype": "0", + "hb": "true", + "currency": bidder.getCurrency(successRequest), + "sdkname": "prebidserver", + "adapterver": bidder.version, + "size": getSizes(&successRequest.Imp[index]), + "tp": successRequest.Site.Name, + } + for key, expectedValue := range expectQueries { + actualValue := rawQuery.Get(key) + if actualValue == "" { + if !(key == "size" || key == "tp") { + t.Errorf("getRawQuery: key %s is required value.", key) + } + } + if actualValue != expectedValue { + t.Errorf("getRawQuery: %s value does not match expected %s, actual %s", key, expectedValue, actualValue) + } + } + } +} + +func TestGetSizes(t *testing.T) { + // Test items + var request *openrtb.Imp + var size string + multiFormatBanner := &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 320, H: 50}}} + noFormatBanner := &openrtb.Banner{Format: []openrtb.Format{}} + nativeFormat := &openrtb.Native{} + + request = &openrtb.Imp{Banner: multiFormatBanner} + size = getSizes(request) + if size != "300×250,320×50" { + t.Errorf("%v does not match size.", multiFormatBanner) + } + request = &openrtb.Imp{Banner: noFormatBanner} + size = getSizes(request) + if size != "" { + t.Errorf("%v does not match size.", noFormatBanner) + } + request = &openrtb.Imp{Native: nativeFormat} + size = getSizes(request) + if size != "" { + t.Errorf("%v does not match size.", nativeFormat) + } +} + +func TestGetCurrency(t *testing.T) { + bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1") + // Test items + var request *openrtb.BidRequest + var currency string + innerDefaultCur := []string{"USD", "JPY"} + usdCur := []string{"USD", "EUR"} + + request = &openrtb.BidRequest{Cur: innerDefaultCur} + currency = bidder.getCurrency(request) + if currency != "JPY" { + t.Errorf("%v does not match currency.", innerDefaultCur) + } + request = &openrtb.BidRequest{Cur: usdCur} + currency = bidder.getCurrency(request) + if currency != "USD" { + t.Errorf("%v does not match currency.", usdCur) + } +} + +func TestCreateAd(t *testing.T) { + // Test items + adgBannerImpId := "test-banner-imp" + adgBannerResponse := adgServerResponse{ + Ad: "\n\n\n\n\n
\n\n
\n\n", + Beacon: "", + Beaconurl: "https://dummy-beacon.com", + Cpm: 50, + Creativeid: "DummyDsp_SdkTeam_supership.jp", + H: 300, + W: 250, + Ttl: 10, + LandingUrl: "", + Scheduleid: "111111", + } + matchBannerTag := "
\n\n
\n" + + adgVastImpId := "test-vast-imp" + adgVastResponse := adgServerResponse{ + Ad: "\n\n\n\n\n
\n\n
\n\n", + Beacon: "", + Beaconurl: "https://dummy-beacon.com", + Cpm: 50, + Creativeid: "DummyDsp_SdkTeam_supership.jp", + H: 300, + W: 250, + Ttl: 10, + LandingUrl: "", + Vastxml: "", + Scheduleid: "111111", + } + matchVastTag := "
" + + bannerAd := createAd(&adgBannerResponse, adgBannerImpId) + if bannerAd != matchBannerTag { + t.Errorf("%v does not match createAd.", adgBannerResponse) + } + vastAd := createAd(&adgVastResponse, adgVastImpId) + if vastAd != matchVastTag { + t.Errorf("%v does not match createAd.", adgVastResponse) + } +} diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json new file mode 100644 index 00000000000..d23a510bee5 --- /dev/null +++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest":{ + "id": "some-request-id", + "site": { + "page": "http://example.com/test.html" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "id": "58278" + } + } + } + ], + "tmax": 500 + }, + "httpCalls": [ + { + "internalRequest": { + "id": "some-request-id", + "site": { + "page": "http://example.com/test.html" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "id": "58278" + } + } + } + ], + "tmax": 500 + }, + "expectedRequest":{ + "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + } + }, + "mockResponse":{ + "status": 200, + "body": { + "ad": "\n \n \n +` + +type ResponseAdUnit struct { + ID string `json:"id"` + CrID string `json:"crid"` + Currency string `json:"currency"` + Price string `json:"price"` + Width string `json:"width"` + Height string `json:"height"` + Code string `json:"code"` + WinURL string `json:"winUrl"` + StatsURL string `json:"statsUrl"` + Error string `json:"error"` +} + +func NewAdOceanBidder(client *http.Client, endpointTemplateString string) *AdOceanAdapter { + a := &adapters.HTTPAdapter{Client: client} + endpointTemplate, err := template.New("endpointTemplate").Parse(endpointTemplateString) + if err != nil { + glog.Fatal("Unable to parse endpoint template") + return nil + } + + whiteSpace := regexp.MustCompile(`\s+`) + + return &AdOceanAdapter{ + http: a, + endpointTemplate: *endpointTemplate, + measurementCode: whiteSpace.ReplaceAllString(measurementCode, " "), + } +} + +type AdOceanAdapter struct { + http *adapters.HTTPAdapter + endpointTemplate template.Template + measurementCode string +} + +func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + + consentString := "" + if request.User != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { + consentString = extUser.Consent + } + } + + var httpRequests []*adapters.RequestData + var errors []error + + for _, auction := range request.Imp { + newHttpRequest, err := a.makeRequest(httpRequests, &auction, request, consentString) + if err != nil { + errors = append(errors, err) + } else if newHttpRequest != nil { + httpRequests = append(httpRequests, newHttpRequest) + } + } + + return httpRequests, errors +} + +func (a *AdOceanAdapter) makeRequest(existingRequests []*adapters.RequestData, imp *openrtb.Imp, request *openrtb.BidRequest, consentString string) (*adapters.RequestData, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error parsing bidderExt object", + } + } + + var adOceanExt openrtb_ext.ExtImpAdOcean + if err := json.Unmarshal(bidderExt.Bidder, &adOceanExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error parsing adOceanExt parameters", + } + } + + addedToExistingRequest := addToExistingRequest(existingRequests, &adOceanExt, imp.ID) + if addedToExistingRequest { + return nil, nil + } + + url, err := a.makeURL(&adOceanExt, imp.ID, request, consentString) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Device != nil { + headers.Add("User-Agent", request.Device.UA) + + if request.Device.IP != "" { + headers.Add("X-Forwarded-For", request.Device.IP) + } else if request.Device.IPv6 != "" { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + } + + if request.Site != nil { + headers.Add("Referer", request.Site.Page) + } + + return &adapters.RequestData{ + Method: "GET", + Uri: url, + Headers: headers, + }, nil +} + +func addToExistingRequest(existingRequests []*adapters.RequestData, newParams *openrtb_ext.ExtImpAdOcean, auctionID string) bool { +requestsLoop: + for _, request := range existingRequests { + endpointURL, _ := url.Parse(request.Uri) + queryParams := endpointURL.Query() + masterID := queryParams["id"][0] + + if masterID == newParams.MasterID { + aids := queryParams["aid"] + for _, aid := range aids { + slaveID := strings.SplitN(aid, ":", 2)[0] + if slaveID == newParams.SlaveID { + continue requestsLoop + } + } + + queryParams.Add("aid", newParams.SlaveID+":"+auctionID) + endpointURL.RawQuery = queryParams.Encode() + newUri := endpointURL.String() + if len(newUri) < maxUriLength { + request.Uri = newUri + return true + } + } + } + + return false +} + +func (a *AdOceanAdapter) makeURL(params *openrtb_ext.ExtImpAdOcean, auctionID string, request *openrtb.BidRequest, consentString string) (string, error) { + endpointParams := macros.EndpointTemplateParams{Host: params.EmitterDomain} + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) + if err != nil { + return "", &errortypes.BadInput{ + Message: "Unable to parse endpoint url template: " + err.Error(), + } + } + + endpointURL, err := url.Parse(host) + if err != nil { + return "", &errortypes.BadInput{ + Message: "Malformed URL: " + err.Error(), + } + } + + randomizedPart := 10000000 + rand.Intn(99999999-10000000) + if request.Test == 1 { + randomizedPart = 10000000 + } + endpointURL.Path = "/_" + strconv.Itoa(randomizedPart) + "/ad.json" + + queryParams := url.Values{} + queryParams.Add("pbsrv_v", adapterVersion) + queryParams.Add("id", params.MasterID) + queryParams.Add("nc", "1") + queryParams.Add("nosecure", "1") + queryParams.Add("aid", params.SlaveID+":"+auctionID) + if consentString != "" { + queryParams.Add("gdpr_consent", consentString) + queryParams.Add("gdpr", "1") + } + if request.User != nil && request.User.BuyerUID != "" { + queryParams.Add("hcuserid", request.User.BuyerUID) + } + endpointURL.RawQuery = queryParams.Encode() + + return endpointURL.String(), nil +} + +func (a *AdOceanAdapter) MakeBids( + internalRequest *openrtb.BidRequest, + externalRequest *adapters.RequestData, + response *adapters.ResponseData, +) (*adapters.BidderResponse, []error) { + if response.StatusCode != http.StatusOK { + return nil, []error{fmt.Errorf("Unexpected status code: %d. Network error?", response.StatusCode)} + } + + requestURL, _ := url.Parse(externalRequest.Uri) + queryParams := requestURL.Query() + auctionIDs := queryParams["aid"] + + bidResponses := make([]ResponseAdUnit, 0) + if err := json.Unmarshal(response.Body, &bidResponses); err != nil { + return nil, []error{err} + } + + var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(auctionIDs)) + var errors []error + var slaveToAuctionIDMap = make(map[string]string, len(auctionIDs)) + + for _, auctionFullID := range auctionIDs { + auctionIDsSlice := strings.SplitN(auctionFullID, ":", 2) + slaveToAuctionIDMap[auctionIDsSlice[0]] = auctionIDsSlice[1] + } + + for _, bid := range bidResponses { + if auctionID, found := slaveToAuctionIDMap[bid.ID]; found { + if bid.Error == "true" { + continue + } + + price, _ := strconv.ParseFloat(bid.Price, 64) + width, _ := strconv.ParseUint(bid.Width, 10, 64) + height, _ := strconv.ParseUint(bid.Height, 10, 64) + adCode, err := a.prepareAdCodeForBid(bid) + if err != nil { + errors = append(errors, err) + continue + } + + parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{ + Bid: &openrtb.Bid{ + ID: bid.ID, + ImpID: auctionID, + Price: price, + AdM: adCode, + CrID: bid.CrID, + W: width, + H: height, + }, + BidType: openrtb_ext.BidTypeBanner, + }) + parsedResponses.Currency = bid.Currency + } + } + + return parsedResponses, errors +} + +func (a *AdOceanAdapter) prepareAdCodeForBid(bid ResponseAdUnit) (string, error) { + sspCode, err := url.QueryUnescape(bid.Code) + if err != nil { + return "", err + } + + adCode := fmt.Sprintf(a.measurementCode, bid.WinURL, bid.StatsURL) + sspCode + + return adCode, nil +} diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go new file mode 100644 index 00000000000..5713b02da27 --- /dev/null +++ b/adapters/adocean/adocean_test.go @@ -0,0 +1,12 @@ +package adocean + +import ( + "net/http" + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "adoceantest", NewAdOceanBidder(new(http.Client), "https://{{.Host}}")) +} diff --git a/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json new file mode 100644 index 00000000000..007a530621a --- /dev/null +++ b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b", + "source": { + "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b" + }, + "tmax": 1000, + "imp": [{ + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "secod-twelve", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaowafpdwlrks" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://192.168.100.203/testing/prebid_server/test.html" + }, + "device": { + "w": 418, + "h": 961 + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Asecod-twelve&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [{ + "id": "adoceanmyaozpniqismex", + "price": "1", + "winurl": "https://win-url.com", + "statsUrl": "https://stats-url.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + }, + { + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "https://win-url2.com", + "statsUrl": "https://stats-url2.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + } + ] + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaozpniqismex", + "impid": "ao-test", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + },{ + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "secod-twelve", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adocean/adoceantest/exemplary/single-banner-impression.json b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json new file mode 100644 index 00000000000..b938a042a80 --- /dev/null +++ b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "9ed903f4-383d-406b-8011-4f06526cb02c", + "source": { + "tid": "9ed903f4-383d-406b-8011-4f06526cb02c" + }, + "tmax": 1000, + "imp": [ + { + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://example.com/test.html" + }, + "device": { + "w": 1280, + "h": 720, + "ip": "192.168.1.1" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": "adoceanmyaozpniqismex", + "price": "1", + "winurl": "https://win-url.com", + "statsUrl": "https://stats-url.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + }, + { + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "", + "statsUrl": "", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "adoceanmyaozpniqismex", + "impid": "ao-test", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adocean/adoceantest/params/race/banner.json b/adapters/adocean/adoceantest/params/race/banner.json new file mode 100644 index 00000000000..f9f38481350 --- /dev/null +++ b/adapters/adocean/adoceantest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" +} diff --git a/adapters/adocean/adoceantest/supplemental/bad-response.json b/adapters/adocean/adoceantest/supplemental/bad-response.json new file mode 100644 index 00000000000..514262d5d2e --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/bad-response.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "9ed903f4-383d-406b-8011-4f06526cb02c", + "source": { + "tid": "9ed903f4-383d-406b-8011-4f06526cb02c" + }, + "tmax": 1000, + "imp": [ + { + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://example.com/test.html" + }, + "device": { + "w": 1280, + "h": 720, + "ip": "192.168.1.1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": "{ key: nil }" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type []adocean.ResponseAdUnit", + "comparison": "literal" + } + ] +} diff --git a/adapters/adocean/adoceantest/supplemental/encode-error.json b/adapters/adocean/adoceantest/supplemental/encode-error.json new file mode 100644 index 00000000000..2f775f98748 --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/encode-error.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "9ed903f4-383d-406b-8011-4f06526cb02c", + "source": { + "tid": "9ed903f4-383d-406b-8011-4f06526cb02c" + }, + "tmax": 1000, + "imp": [ + { + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://example.com/test.html" + }, + "device": { + "w": 1280, + "h": 720, + "ip": "192.168.1.1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": "adoceanmyaozpniqismex", + "price": "1", + "winurl": "", + "statsUrl": "", + "code": " %a", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + } + ] + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "invalid URL escape \"%a\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/adocean/adoceantest/supplemental/network-error.json b/adapters/adocean/adoceantest/supplemental/network-error.json new file mode 100644 index 00000000000..7a5fa8fd18e --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/network-error.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "9ed903f4-383d-406b-8011-4f06526cb02c", + "source": { + "tid": "9ed903f4-383d-406b-8011-4f06526cb02c" + }, + "tmax": 1000, + "imp": [ + { + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://example.com/test.html" + }, + "device": { + "w": 1280, + "h": 720, + "ip": "192.168.1.1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Network error?", + "comparison": "literal" + } + ] +} diff --git a/adapters/adocean/adoceantest/supplemental/no-bid.json b/adapters/adocean/adoceantest/supplemental/no-bid.json new file mode 100644 index 00000000000..ed81bb35114 --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/no-bid.json @@ -0,0 +1,159 @@ +{ + "mockBidRequest": { + "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b", + "source": { + "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b" + }, + "tmax": 1000, + "imp": [{ + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "ao-test-two", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaowafpdwlrks" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "ao-test-three", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaowafpdwlrks" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://localhost/prebid_server/test.html" + }, + "device": { + "w": 418, + "h": 961 + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [{ + "id": "adoceanmyaozpniqismex", + "error": "true" + }, + { + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "https://win-url2.com", + "statsUrl": "https://stats-url2.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + } + ] + } + }, { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [{ + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "https://win-url3.com", + "statsUrl": "https://stats-url3.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + }] + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-two", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }, { + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-three", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adocean/adoceantest/supplemental/no-impression.json b/adapters/adocean/adoceantest/supplemental/no-impression.json new file mode 100644 index 00000000000..8f2a8eef351 --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/no-impression.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "9ed903f4-383d-406b-8011-4f06526cb02c", + "source": { + "tid": "9ed903f4-383d-406b-8011-4f06526cb02c" + }, + "tmax": 1000, + "imp": [], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://example.com/test.html" + }, + "device": { + "w": 1280, + "h": 720, + "ip": "192.168.1.1" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ] +} diff --git a/adapters/adocean/adoceantest/supplemental/requests-merge.json b/adapters/adocean/adoceantest/supplemental/requests-merge.json new file mode 100644 index 00000000000..9b5eb39aee2 --- /dev/null +++ b/adapters/adocean/adoceantest/supplemental/requests-merge.json @@ -0,0 +1,179 @@ +{ + "mockBidRequest": { + "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b", + "source": { + "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b" + }, + "tmax": 1000, + "imp": [{ + "id": "ao-test", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaozpniqismex" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "ao-test-two", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaowafpdwlrks" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "ao-test-three", + "ext": { + "bidder": { + "emiter": "myao.adocean.pl", + "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", + "slaveId": "adoceanmyaowafpdwlrks" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://localhost/prebid_server/test.html" + }, + "device": { + "w": 418, + "h": 961 + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [{ + "id": "adoceanmyaozpniqismex", + "price": "1", + "winurl": "https://win-url.com", + "statsUrl": "https://stats-url.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + }, + { + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "https://win-url2.com", + "statsUrl": "https://stats-url2.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + } + ] + } + }, { + "expectedRequest": { + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + }, + "mockResponse": { + "status": 200, + "body": [{ + "id": "adoceanmyaowafpdwlrks", + "price": "1", + "winurl": "https://win-url3.com", + "statsUrl": "https://stats-url3.com", + "code": " ", + "currency": "EUR", + "minFloorPrice": "0.01", + "width": "300", + "height": "250", + "crid": "0af345b42983cc4bc0", + "ttl": "300" + }] + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaozpniqismex", + "impid": "ao-test", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }, { + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-two", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }, { + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-three", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adocean/params_test.go b/adapters/adocean/params_test.go new file mode 100644 index 00000000000..1a88c4716e0 --- /dev/null +++ b/adapters/adocean/params_test.go @@ -0,0 +1,50 @@ +package adocean + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdOcean, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adocean params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdOcean, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`, +} + +var invalidParams = []string{ + `{}`, + `{"masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`, + `{"emiter": "myao.adocean.pl", "slaveId": "adoceanmyaozpniqismex"}`, + `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7"}`, + `{"emiter": "", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`, + `{"emiter": "myao.adocean.pl", "", "slaveId": "adoceanmyaozpniqismex"}`, + `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": ""}`, + `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7Z utQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`, + `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmy iqismex"}`, +} diff --git a/adapters/adocean/usersync.go b/adapters/adocean/usersync.go new file mode 100644 index 00000000000..650e517a578 --- /dev/null +++ b/adapters/adocean/usersync.go @@ -0,0 +1,12 @@ +package adocean + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdOceanSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adocean", 328, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/adocean/usersync_test.go b/adapters/adocean/usersync_test.go new file mode 100644 index 00000000000..9ca81b98cb4 --- /dev/null +++ b/adapters/adocean/usersync_test.go @@ -0,0 +1,34 @@ +package adocean + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdOceanSyncer(t *testing.T) { + syncURL := "https://sync-host.com/redataredir/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3DUUID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdOceanSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "consent-string", + }, + }) + + assert.NoError(t, err) + assert.Equal( + t, + "https://sync-host.com/redataredir/?gdpr=1&gdpr_consent=consent-string&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D1%26gdpr_consent%3Dconsent-string%26uid%3DUUID", + syncInfo.URL, + ) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 328, syncer.GDPRVendorID()) +} diff --git a/config/config.go b/config/config.go index 2d49b0605a1..7e4e4196cd9 100755 --- a/config/config.go +++ b/config/config.go @@ -500,6 +500,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") + // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -692,6 +693,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}") v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") + v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 3bf1b6a5c82..e6cce7a643b 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -12,6 +12,7 @@ import ( "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" "github.com/prebid/prebid-server/adapters/admixer" + "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adtelligent" @@ -84,6 +85,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdkernel: adkernel.NewAdkernelAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernel))].Endpoint), openrtb_ext.BidderAdkernelAdn: adkernelAdn.NewAdkernelAdnAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernelAdn))].Endpoint), openrtb_ext.BidderAdmixer: admixer.NewAdmixerBidder(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdmixer))].Endpoint), + openrtb_ext.BidderAdOcean: adocean.NewAdOceanBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdOcean))].Endpoint), openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), openrtb_ext.BidderAdpone: adpone.NewAdponeBidder(cfg.Adapters[string(openrtb_ext.BidderAdpone)].Endpoint), openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 6660ddac946..aa8e959f7a5 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -31,6 +31,7 @@ const ( BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdpone BidderName = "adpone" BidderAdmixer BidderName = "admixer" + BidderAdOcean BidderName = "adocean" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderAJA BidderName = "aja" @@ -98,6 +99,7 @@ var BidderMap = map[string]BidderName{ "adkernel": BidderAdkernel, "adkernelAdn": BidderAdkernelAdn, "admixer": BidderAdmixer, + "adocean": BidderAdOcean, "adpone": BidderAdpone, "adtelligent": BidderAdtelligent, "advangelists": BidderAdvangelists, diff --git a/openrtb_ext/imp_adocean.go b/openrtb_ext/imp_adocean.go new file mode 100644 index 00000000000..e690e929778 --- /dev/null +++ b/openrtb_ext/imp_adocean.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpAdOcean struct { + EmitterDomain string `json:"emiter"` + MasterID string `json:"masterId"` + SlaveID string `json:"slaveId"` +} diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml new file mode 100644 index 00000000000..2f31fe92eaf --- /dev/null +++ b/static/bidder-info/adocean.yaml @@ -0,0 +1,6 @@ +maintainer: + email: "aoteam@gemius.com" +capabilities: + site: + mediaTypes: + - banner diff --git a/static/bidder-params/adocean.json b/static/bidder-params/adocean.json new file mode 100644 index 00000000000..7530c64784c --- /dev/null +++ b/static/bidder-params/adocean.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdOcean Adapter Params", + "description": "A schema which validates params accepted by the AdOcean adapter", + "type": "object", + "properties": { + "emiter": { + "type": "string", + "description": "AdOcean emiter", + "pattern": ".+" + }, + "masterId": { + "type": "string", + "description": "Master's id", + "pattern": "^[\\w.]+$" + }, + "slaveId": { + "type": "string", + "description": "Slave's id", + "pattern": "^adocean[\\w.]+$" + } + }, + "required": ["emiter", "masterId", "slaveId"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 2c9cf59781b..1d8c8a0794c 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -10,6 +10,7 @@ import ( "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" "github.com/prebid/prebid-server/adapters/admixer" + "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adpone" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" @@ -77,6 +78,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdmixer, admixer.NewAdmixerSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdOcean, adocean.NewAdOceanSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdpone, adpone.NewadponeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 221302dd333..050e1039000 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -19,6 +19,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdkernel): syncConfig, string(openrtb_ext.BidderAdkernelAdn): syncConfig, string(openrtb_ext.BidderAdmixer): syncConfig, + string(openrtb_ext.BidderAdOcean): syncConfig, string(openrtb_ext.BidderAdpone): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, From 8db5479aecd52455facbcc9484a8bb8f128984e5 Mon Sep 17 00:00:00 2001 From: trchandraprakash <47793448+trchandraprakash@users.noreply.github.com> Date: Wed, 6 May 2020 07:29:16 -0700 Subject: [PATCH 072/603] LunaMedia Adapter (#1285) Co-authored-by: Chandra Prakash --- adapters/lunamedia/lunamedia.go | 236 ++++++++++++++++++ adapters/lunamedia/lunamedia_test.go | 10 + .../lunamediatest/exemplary/banner.json | 95 +++++++ .../lunamediatest/exemplary/video.json | 83 ++++++ .../lunamediatest/params/race/banner.json | 4 + .../lunamediatest/params/race/video.json | 4 + .../lunamediatest/supplemental/checkImp.json | 14 ++ .../lunamediatest/supplemental/compat.json | 80 ++++++ .../lunamediatest/supplemental/ext.json | 33 +++ .../supplemental/missingpub.json | 35 +++ .../supplemental/responseCode.json | 78 ++++++ .../supplemental/responsebid.json | 79 ++++++ .../lunamediatest/supplemental/site.json | 103 ++++++++ .../lunamediatest/supplemental/size.json | 28 +++ adapters/lunamedia/params_test.go | 45 ++++ adapters/lunamedia/usersync.go | 12 + adapters/lunamedia/usersync_test.go | 31 +++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_lunamedia.go | 6 + static/bidder-info/lunamedia.yaml | 13 + static/bidder-params/lunamedia.json | 18 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1016 insertions(+) create mode 100644 adapters/lunamedia/lunamedia.go create mode 100644 adapters/lunamedia/lunamedia_test.go create mode 100644 adapters/lunamedia/lunamediatest/exemplary/banner.json create mode 100644 adapters/lunamedia/lunamediatest/exemplary/video.json create mode 100644 adapters/lunamedia/lunamediatest/params/race/banner.json create mode 100644 adapters/lunamedia/lunamediatest/params/race/video.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/checkImp.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/compat.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/ext.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/missingpub.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/responseCode.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/responsebid.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/site.json create mode 100644 adapters/lunamedia/lunamediatest/supplemental/size.json create mode 100644 adapters/lunamedia/params_test.go create mode 100644 adapters/lunamedia/usersync.go create mode 100644 adapters/lunamedia/usersync_test.go create mode 100755 openrtb_ext/imp_lunamedia.go create mode 100644 static/bidder-info/lunamedia.yaml create mode 100644 static/bidder-params/lunamedia.json diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go new file mode 100644 index 00000000000..51906884331 --- /dev/null +++ b/adapters/lunamedia/lunamedia.go @@ -0,0 +1,236 @@ +package lunamedia + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type LunaMediaAdapter struct { + EndpointTemplate template.Template +} + +//MakeRequests prepares request information for prebid-server core +func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + if len(request.Imp) == 0 { + errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) + return nil, errs + } + pub2impressions, imps, err := getImpressionsInfo(request.Imp) + if len(imps) == 0 { + return nil, err + } + errs = append(errs, err...) + + if len(pub2impressions) == 0 { + return nil, errs + } + + result := make([]*adapters.RequestData, 0, len(pub2impressions)) + for k, imps := range pub2impressions { + bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) + if err != nil { + errs = append(errs, err) + return nil, errs + } else { + result = append(result, bidRequest) + } + } + return result, errs +} + +// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts +func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp, []openrtb.Imp, []error) { + errors := make([]error, 0, len(imps)) + resImps := make([]openrtb.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp) + + for _, imp := range imps { + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + if err := validateImpression(impExt); err != nil { + errors = append(errors, err) + continue + } + //dispatchImpressions + //Group impressions by LunaMedia-specific parameters `pubid + if err := compatImpression(&imp); err != nil { + errors = append(errors, err) + continue + } + if res[*impExt] == nil { + res[*impExt] = make([]openrtb.Imp, 0) + } + res[*impExt] = append(res[*impExt], imp) + resImps = append(resImps, imp) + } + return res, resImps, errors +} + +func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error { + if impExt.PublisherID == "" { + return &errortypes.BadInput{Message: "No pubid value provided"} + } + return nil +} + +//Alter impression info to comply with LunaMedia platform requirements +func compatImpression(imp *openrtb.Imp) error { + imp.Ext = nil //do not forward ext to LunaMedia platform + if imp.Banner != nil { + return compatBannerImpression(imp) + } + return nil +} + +func compatBannerImpression(imp *openrtb.Imp) error { + // Create a copy of the banner, since imp is a shallow copy of the original. + + bannerCopy := *imp.Banner + banner := &bannerCopy + //As banner.w/h are required fields for LunaMedia platform - take the first format entry + if banner.W == nil || banner.H == nil { + if len(banner.Format) == 0 { + return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"} + } + format := banner.Format[0] + banner.Format = banner.Format[1:] + banner.W = &format.W + banner.H = &format.H + imp.Banner = banner + } + return nil +} + +func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + var LunaMediaExt openrtb_ext.ExtImpLunaMedia + if err := json.Unmarshal(bidderExt.Bidder, &LunaMediaExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + return &LunaMediaExt, nil +} + +func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) (*adapters.RequestData, error) { + newBidRequest := createBidRequest(prebidBidRequest, params, imps) + reqJSON, err := json.Marshal(newBidRequest) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + url, err := adapter.buildEndpointURL(params) + if err != nil { + return nil, err + } + + return &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: headers}, nil +} + +func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) *openrtb.BidRequest { + bidRequest := *prebidBidRequest + bidRequest.Imp = imps + for idx := range bidRequest.Imp { + imp := &bidRequest.Imp[idx] + imp.TagID = params.Placement + } + if bidRequest.Site != nil { + // Need to copy Site as Request is a shallow copy + siteCopy := *bidRequest.Site + bidRequest.Site = &siteCopy + bidRequest.Site.Publisher = nil + bidRequest.Site.Domain = "" + } + if bidRequest.App != nil { + // Need to copy App as Request is a shallow copy + appCopy := *bidRequest.App + bidRequest.App = &appCopy + bidRequest.App.Publisher = nil + } + return &bidRequest +} + +// Builds enpoint url based on adapter-specific pub settings from imp.ext +func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLunaMedia) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} + return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) +} + +//MakeBids translates LunaMedia bid response to prebid-server specific format +func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var msg = "" + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode != http.StatusOK { + msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + msg = fmt.Sprintf("Bad server response: %d", err) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + if len(bidResp.SeatBid) != 1 { + var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), + }) + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + } + return openrtb_ext.BidTypeBanner +} + +// NewLunaMediaAdapter to be called in prebid-server core to create LunaMedia adapter instance +func NewLunaMediaBidder(endpointTemplate string) adapters.Bidder { + template, err := template.New("endpointTemplate").Parse(endpointTemplate) + if err != nil { + return nil + } + return &LunaMediaAdapter{EndpointTemplate: *template} +} diff --git a/adapters/lunamedia/lunamedia_test.go b/adapters/lunamedia/lunamedia_test.go new file mode 100644 index 00000000000..924e6a774b1 --- /dev/null +++ b/adapters/lunamedia/lunamedia_test.go @@ -0,0 +1,10 @@ +package lunamedia + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "lunamediatest", NewLunaMediaBidder("http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")) +} diff --git a/adapters/lunamedia/lunamediatest/exemplary/banner.json b/adapters/lunamedia/lunamediatest/exemplary/banner.json new file mode 100644 index 00000000000..3b5c417f169 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/exemplary/banner.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "id": "testimpid", + "tagid": "dummyplacement", + "banner": { + "format": [{ + "w": 320, + "h": 250 + }, { + "w": 320, + "h": 300 + }], + "w": 320, + "h": 250 + } + + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/lunamedia/lunamediatest/exemplary/video.json b/adapters/lunamedia/lunamediatest/exemplary/video.json new file mode 100644 index 00000000000..82217373e2e --- /dev/null +++ b/adapters/lunamedia/lunamediatest/exemplary/video.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "id": "testimpid", + "tagid": "dummyplacement", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/params/race/banner.json b/adapters/lunamedia/lunamediatest/params/race/banner.json new file mode 100644 index 00000000000..2eed8f2ec4e --- /dev/null +++ b/adapters/lunamedia/lunamediatest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/params/race/video.json b/adapters/lunamedia/lunamediatest/params/race/video.json new file mode 100644 index 00000000000..2eed8f2ec4e --- /dev/null +++ b/adapters/lunamedia/lunamediatest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/checkImp.json b/adapters/lunamedia/lunamediatest/supplemental/checkImp.json new file mode 100644 index 00000000000..ca48812b4df --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/checkImp.json @@ -0,0 +1,14 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test", + "domain": "test.com" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/compat.json b/adapters/lunamedia/lunamediatest/supplemental/compat.json new file mode 100644 index 00000000000..5b84d3a5a39 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/compat.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [{ + "w": 320, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "banner": { + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/lunamedia/lunamediatest/supplemental/ext.json b/adapters/lunamedia/lunamediatest/supplemental/ext.json new file mode 100644 index 00000000000..3cfb878bd47 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/ext.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + ] + }, +"expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/missingpub.json b/adapters/lunamedia/lunamediatest/supplemental/missingpub.json new file mode 100644 index 00000000000..b088917afa3 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/missingpub.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "", + "placement": "dummyplacement" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No pubid value provided", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/responseCode.json b/adapters/lunamedia/lunamediatest/supplemental/responseCode.json new file mode 100644 index 00000000000..739af044b29 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/responseCode.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "yu", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=yu", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "body": { + "seatbid": [] + } + } + } + + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/responsebid.json b/adapters/lunamedia/lunamediatest/supplemental/responsebid.json new file mode 100644 index 00000000000..e9d8c3c543d --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/responsebid.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "yu", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=yu", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status":200, + "body": { + "seatbid": [] + } + } + } + + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/site.json b/adapters/lunamedia/lunamediatest/supplemental/site.json new file mode 100644 index 00000000000..81d71554f38 --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/site.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/supplemental/size.json b/adapters/lunamedia/lunamediatest/supplemental/size.json new file mode 100644 index 00000000000..77228559eee --- /dev/null +++ b/adapters/lunamedia/lunamediatest/supplemental/size.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test", + "domain": "test.com" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Expected at least one banner.format entry or explicit w/h", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lunamedia/params_test.go b/adapters/lunamedia/params_test.go new file mode 100644 index 00000000000..b4faeea1f77 --- /dev/null +++ b/adapters/lunamedia/params_test.go @@ -0,0 +1,45 @@ +package lunamedia + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderLunaMedia, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected LunaMedia params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderLunaMedia, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubid": "19f1b372c7548ec1fe734d2c9f8dc688"}`, +} + +var invalidParams = []string{ + `{"publisher": "19f1b372c7548ec1fe734d2c9f8dc688"}`, + `nil`, + ``, + `[]`, + `true`, +} diff --git a/adapters/lunamedia/usersync.go b/adapters/lunamedia/usersync.go new file mode 100644 index 00000000000..7ad54e384a1 --- /dev/null +++ b/adapters/lunamedia/usersync.go @@ -0,0 +1,12 @@ +package lunamedia + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewLunaMediaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("lunamedia", 0, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/lunamedia/usersync_test.go b/adapters/lunamedia/usersync_test.go new file mode 100644 index 00000000000..c9fe2032d2c --- /dev/null +++ b/adapters/lunamedia/usersync_test.go @@ -0,0 +1,31 @@ +package lunamedia + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestLunaMediaSyncer(t *testing.T) { + syncURL := "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=$UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewLunaMediaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "A", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 7e4e4196cd9..a7132edbc81 100755 --- a/config/config.go +++ b/config/config.go @@ -522,6 +522,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=184932&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -722,6 +723,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest") v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") + v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index e6cce7a643b..17814b3639a 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -40,6 +40,7 @@ import ( "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/lunamedia" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/nanointeractive" @@ -113,6 +114,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), + openrtb_ext.BidderLunaMedia: lunamedia.NewLunaMediaBidder(cfg.Adapters[string(openrtb_ext.BidderLunaMedia)].Endpoint), openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), openrtb_ext.BidderNanoInteractive: nanointeractive.NewNanoIneractiveBidder(cfg.Adapters[string(openrtb_ext.BidderNanoInteractive)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index aa8e959f7a5..c9b7f7a0519 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -58,6 +58,7 @@ const ( BidderKubient BidderName = "kubient" BidderLifestreet BidderName = "lifestreet" BidderLockerDome BidderName = "lockerdome" + BidderLunaMedia BidderName = "lunamedia" BidderMarsmedia BidderName = "marsmedia" BidderMgid BidderName = "mgid" BidderNanoInteractive BidderName = "nanointeractive" @@ -127,6 +128,7 @@ var BidderMap = map[string]BidderName{ "kubient": BidderKubient, "lifestreet": BidderLifestreet, "lockerdome": BidderLockerDome, + "lunamedia": BidderLunaMedia, "marsmedia": BidderMarsmedia, "mgid": BidderMgid, "nanointeractive": BidderNanoInteractive, diff --git a/openrtb_ext/imp_lunamedia.go b/openrtb_ext/imp_lunamedia.go new file mode 100755 index 00000000000..e7e4dd6593c --- /dev/null +++ b/openrtb_ext/imp_lunamedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpLunaMedia struct { + PublisherID string `json:"pubid"` + Placement string `json:"placement,omitempty"` +} diff --git a/static/bidder-info/lunamedia.yaml b/static/bidder-info/lunamedia.yaml new file mode 100644 index 00000000000..4cabdc4a381 --- /dev/null +++ b/static/bidder-info/lunamedia.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "josh@lunamedia.io" +capabilities: + site: + mediaTypes: + - banner + - video + + app: + mediaTypes: + - banner + - video + diff --git a/static/bidder-params/lunamedia.json b/static/bidder-params/lunamedia.json new file mode 100644 index 00000000000..1aa18cee6b9 --- /dev/null +++ b/static/bidder-params/lunamedia.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "LunaMedia Adapter Params", + "description": "A schema which validates params accepted by the LunaMedia adapter", + "type": "object", + "properties": { + "pubid": { + "type": "string", + "description": "An id used to identify LunaMedia publisher.", + "minLength": 8 + }, + "placement": { + "type": "string", + "description": "A placement created on adserver." + } + }, + "required": ["pubid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1d8c8a0794c..42c93d652b3 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -34,6 +34,7 @@ import ( "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/lunamedia" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/nanointeractive" @@ -102,6 +103,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 050e1039000..44ff15bd5fe 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -43,6 +43,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, + string(openrtb_ext.BidderLunaMedia): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, string(openrtb_ext.BidderNanoInteractive): syncConfig, From 42d52814780de6cecadcdca84aaefd1f594688b7 Mon Sep 17 00:00:00 2001 From: Mathieu Pheulpin Date: Wed, 6 May 2020 08:38:11 -0700 Subject: [PATCH 073/603] [Sharethrough] Add CCPA support (#1263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle gzip responses from ad server correctly * Bump to version 8 * [Go Modules] Add proxy (#1079) * Add SSL cert for accessing stored request API (#1087) * [misspell] fix a misspell (#1102) * update static bidder params for rubicon video to follow the json marshalling names (#1100) * Switching yieldmo auction endpoint from http to https (#1103) * Add Datablocks Adapter (#1095) * datablocks bid adapter * ttx * add test json * add coverage * redo ttx * formatted * better error handling * additional tests and recomended fixes * Adding translatecategories flag to includebrandcategory (#1098) * Making IAB category translation optional with translatecategories boolean in request * Updating exchange unit tests to remove extra bids * Updates from code review comments * Removed comment about default TranslateCategories value * Changed translateCat to translateCategories in tests * Combined helper functions in exchange_test related to TranslateCategories * Bid floor (#1085) * Currency handling fix (#1097) * facebook adapter refactor (#1064) * Kubient adapter (#1094) * [synacormedia] Update user sync url to be https (#1115) This detail was missed while setting up the adapter, but we would like to use https for the user sync. * Remove Go 1.11 Build Target (#1109) * Set "Secure" on Same SIte cookies (#1119) * TripleliftNative Adapter (#1114) * ignore swp files * start small * start really small * add a user sync * justify * triplelift adapter * add our endpoint * fix syntax * config stuff * compiler fixes * more config * add params * making progress * make our ext more exty * start making responses * more logic * fix compilation errors * can we just nil this out? * augment our json * radically simplify our json * fix errs * infer the bid type * fix syntax * fix comilation errors * rename * fix compilation error * config stuff * simplify params * more config stuff * fixes * revert this * fix up the extension * getting closer * add a test * update config * update bidder params * add the floor here, too * add a usersync test * validation, ws, and a test * update tests * fix test * update email * why not * change email * preprocess requests * do some parsing * take care of some errors * floor is optional * ws * remove native * everything is either banner or video * this should be a float * floor to floor * fix compilation errors * add some tests * more tests * more tests * simplify * more progress * format * ws * rm * don't need this * fix test * fix test * don't ignore swap * change line back * report an error if there are no valid impressions for triplelift * check for either a Banner or Video object on the impression * more tests * mv * more tests * update triplelift end point * send native * ws * start changing tests * fix more tests * update config * add redirect to triplelift usersync * fix supplier id in triplelift_test * update tl usersync endpoint and test * fix tl supplier id in test json * update usersync test template * adjust inconsistency with test and sync url * mv * update packages * mv * mv * update * fix compilation errors * rename * rename some stuff * rename * rename * fix some compilation errors * ws * ws * add the extra info * add some extra info * add some files back * ws and such * updates * ws * fix compilation error * mv * rename * Revert "rename" This reverts commit 1b77c72e1eeee580148540fbdd880e70bf699709. * Revert "mv" This reverts commit 52a134ddfaf531fe6235e4751935d4266a36e78f. * it builds * cp a file * cp another file * fix a test * fix test * add the extra info * ws * add some logic * edit comment * it compiles * this is now public * call this * add the function * return nil * seems to be working * ws * seems to be working * ws * mv * starting to work * ws * add a new function * ws * fix tests * bug fix * update some stuff * revert * take out prints * fix up diff * fix up diff * update ws * fix * ws * omit the triplelift endppint * Revert "omit the triplelift endppint" This reverts commit 7abc3e46f0fbba39041da6fff7bb2335adc1fece. * populate the endpoint through the extinfo * ws * set disabled to be default * ws * update types * fixing tests * making progres * fix tests * fix tests * more fixes for tests * fixed tests * just use a comment * get rid of endpoint * restore endpoint * add some errors around unmarshalling * ws * ws * use the literal * ws * ws * update json * simplify * ws * restore tests * fail fast when grabbing invcode * use the right type * use a different error type * bump code coverage * add a new test * change error type * ws * break out test into its own function * JSON block that has a full data-center specific URL cache info (#1104) * Update Dockerfile and Makefile (#1099) * Add option for running tests as part of the docker image building * Update Makefile - Add ability to execute adapter specific tests - Execute targets for "all" rather than just printing the target name and usage - Remove use of non-existing "install" target from .PHONY targets - Remove "build" as a dependency for "image" * enable app requests for audience network (#1122) * [docs] fix markdown title (#1124) * Prometheus Refactor (#1108) * update default sync url (#1127) * Update sync url for BidderGrid adapter (#1120) * [SonarCloud] Legacy auction endpoint (#1017) * [currency converter] allow to deduce reverse rate (#1126) This CL allows the currency rate currency to deduce a currency rate even if not directly defined in the table but the reverse rate is present. E.q. USD => EUR is 1.0897 EUR => USD is not set Old behavior when asking rate from EUR to USD will not be found, New behavior is using the known reverse rate to deduce the rate. Rate for 2 USD will be 2 * (1 / 1.0897) * Updated handleError arguments to be pointers for video endpoint (#1128) * Updated handleError arguments to be pointers for video endpoint * Removing unneeded pointer to http.ResponseWriter * Adding units test for update to handleError * Revert changes to GetExtCacheData() made in #1104 (#1130) (#1131) * Better native request validation (#1132) * require the caller to define native assets[...].ID (#1123) * require the caller to define native assets[...].ID * Update assets-with-partial-ids.json * CCPA Phase 1: AMP Endpoint (#1125) * facebook: removed Auth-Token from header (replaced by authentication_id in the request body) (#1113) * Setuid Fix (#1121) * Update http refresh to use url builder. Fixes #1065 (#1133) * Add mapping of user.ext.eids[] for LiveIntent in Rubicon bidder (#1089) * support facebook app_secret config param (#1139) * CCPA Phase 1: Cookie Sync (#1135) * null check banner.h (#1142) * Add Pubnative Adapter (#1134) * Adding the passing of CCPA value to the bid request for video endpoint (#1143) * first draft (#1137) * CCPA Phase 2: Enforcement (#1138) * Gamoshi Adapter: Update cookie sync (#1146) * Simplify static/bidder-params/triplelift_native.json (#1152) * Added US Privacy support in TheMediaGrid server adapter (#1147) * Add TheMediaGrid server adapter * Add video support in TheMediaGrid s2s adapter * Update sync url for TheMediaGrid s2s adapter * Added CCPA support for TheMediaGrid s2s adapter * Fix sync url for TheMediaGrid adapter * CCPA User Sync Updates (#1153) * Marsmedia - add new bidder (#1118) * Add Applogy adapter (#1151) * enforce video.size_id for video imps in rubicon adapter (#1101) * Updated PubMatic endpoint to use https (#1155) * Update Example AppNexus Placement ID (#1160) * Fix Currency Converter Doesn't Output CUR (#1154) * Add custom JSON req/resp data to the analytics logging… (#1145) * Add custom JSON req/resp data to the analytics logging for the /openrtb2/video endpoint. * Add calls in unit tests to cover logging and jsonify of video object. * CCPA User Sync URL Updates (#1157) * Fixes audienceNetwork adapter ignoring banner.format sizes. (#1164) * adding yieldmo vendor id to usersync (#1166) * Add SmartRTB adapter (#1071) * Added new adapter for CPMStar ad network banners and video (#1159) * Update the Conversant sync pixel (#1161) * Add imp.ext.is_rewarded_inventory flag for rewarded video in Rubicon (#1170) * [currencies] fix GetInfo() null ref issue (#1169) This CL fixes the null ref on `RateConverter.GetInfo()` when rates are nil. Issue: #1136 * Fix triplelift User Sync (#1173) * Enhance Message For Cache Errors (#1175) * Fix PubMatic Usersync URL (#1178) Co-authored-by: pm-isha-bharti * [Synacormedia] Add tagId bidder parameter (#1165) * Remove all non-secure calls from eplanning adapter (#1179) * Expose Cache HTTP Settings (#1184) * Adding bid rejection messages to debug response (#1181) * Adds timeout notifications for Facebook (#1182) * VIS.X: added app type support (#1194) * Add Adoppler bidder support. (#1186) * Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter. * Adding support for deal prefixes (#1183) * updating default hard-coded list of certs (#1201) Co-authored-by: Shalmali Patil * add admixer adapter (#1195) * Adding copying of gdpr consent string to openrtb bid request (#1189) * Adding copying of gdpr consent string to openrtb bid request * Updated video request to use OpenRTB Video and User objects * Fixing unit test failure message * Updates from code review comments * Updating unit test initialization * Updated mimes array construction * fix conversant sync pixel (#1208) * openx adapter: forward bid response currency in openx adapter if set (#1211) it was always set to the default USD before * add ucfunnel adapter (#1192) * Update required params for TheMediaGrid adapter (#1188) * add zeroclickfraud adapter (#1207) * add zeroclickfraud adapter * fixes for PR * fix casing of Zeroclickfraud * Fix Adform's parameters regex (#1214) * Added adform info file * Added Adform adapter and bidder * Updates from master * Removed usersyncInfo from Adform adapter. Inverted Imp type check. * Removed excessive loop * Updated with the last master * Create readme file for adform * Fix Adform's parameters regex Motivation: catastrophic backtracking during regex execution Details: - https://regex101.com/r/NNQrWq/1 - string to check "url_domain:keskustelu.suomi24.fi,url_path:/matkailu/matkakohteet/aasia,layout:lg,categories:Matkailu,main_category:Matkailu" Co-authored-by: v.statkevich Co-authored-by: Olga Linkevich * If Device.UA is not present in request body, init it with user-agent from header (#1219) * If Device.UA is not present in request body, init it with user-agent from request header if it's present * Moved User-Agent handler to parseVideoRequest func and added unit test * Minor clean up Co-authored-by: Veronika Solovei * Queued request timeout (#1217) Co-authored-by: Veronika Solovei * docs: adding currency support section (#1199) * Add ValueImpression Adapter (#1204) * Kidoz adapter (#1210) Co-authored-by: Ryan Haksi * Update auction.md (#1224) Fix type * Update auction.md (#1225) Fix typo. * Added logging to cache for video endpoint (#1220) * WIP added logging to cache for video endpoint * Updating cache call to use TTL from config * Updates from initial feedback * Log now includes HTTP headers * Fixed caching to use a new cache entry rather than appending to the VAST * Added feature where is query is set, the test flag is set in the request * Updated recorded response and handleError * Updates from code review comments * Changed recorded output to be only the debug ext * Removed extra marhal calls * Changed cache to be an endpoint dependency * Added debugLog struct to hold all debug related info * Numerous smaller changes * Further code cleanup and added unit tests for debug changes * Added missing error checks * Added unit test for error case * added VISX vendor ID for usersyncing (#1229) Co-authored-by: Aadesh Patel * First pass at phase 1 TCF 2.0 support (#1228) * First pass at phase 1 TCF 2.0 support * minor fixes * Update go-gdpr library and fix stuff * Fixes for PR comments * Updated price granularity unmarshal to accept empty values and ranges (#1230) * Update vendorID for TheMediaGrid s2s Bid Adapter (#1232) * treat 204 from FAN as a no bids response (#1233) Co-authored-by: Aadesh Patel * AMP CCPA Fix (#1187) * Update rubicon.md (#1234) * adding schain interface (#1203) * added Rewarded Video section (#1200) also edited all examples so they include the full openRTB context * nanointeractive adapter (#1213) * nanointeractive adapter * nanointeractive adapter, changes after review * nanointeractive adapter * nanointeractive adapter, changes after review * formatting * Typos Fix (#1236) * Fix Typo * Fixed More Typos * Moved hb_pc_cat_dur modification to be before caching (#1250) * Handle CCPA + enable gzip response [#169984259] * Addressing review (#273) [#169984259] * Remove custom gzip logic (#280) * Getting rid of custom gzip logic [#169984259] * Restore prod ad server url [#169984259] Co-authored-by: Benjamin Co-authored-by: guscarreon Co-authored-by: Aadesh Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: htang555 Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: ah-tappx <46002207+ah-tappx@users.noreply.github.com> Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Marsel Co-authored-by: Corey Kress Co-authored-by: Scott Kay Co-authored-by: Kevin Kerr Co-authored-by: Mansi Nahar Co-authored-by: Benjamin Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Austin Bischoff Co-authored-by: rpanchyk Co-authored-by: Florian Hartwig Co-authored-by: Salomon Rada Co-authored-by: vladi-mmg Co-authored-by: Aleksei Lin Co-authored-by: PubMatic-OpenWrap Co-authored-by: jmaynardxandr <46759873+jmaynardxandr@users.noreply.github.com> Co-authored-by: evanmsmrtb Co-authored-by: CPMStar Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: pm-isha-bharti Co-authored-by: Seba Perez Co-authored-by: Michael Kuryshev Co-authored-by: Viacheslav Chimishuk Co-authored-by: Shalmali Patil Co-authored-by: DmitryStashkevich <34479135+DmitryStashkevich@users.noreply.github.com> Co-authored-by: vstatkevich Co-authored-by: v.statkevich Co-authored-by: Olga Linkevich Co-authored-by: Veronika Solovei Co-authored-by: Veronika Solovei Co-authored-by: bretg Co-authored-by: thuyhq <61451682+thuyhq@users.noreply.github.com> Co-authored-by: rhaksi-kidoz <61601767+rhaksi-kidoz@users.noreply.github.com> Co-authored-by: Ryan Haksi Co-authored-by: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Aadesh Patel Co-authored-by: Rade Popovic <32302052+nanointeractive@users.noreply.github.com> --- adapters/sharethrough/butler.go | 11 +++++++++++ adapters/sharethrough/butler_test.go | 2 ++ adapters/sharethrough/sharethrough.go | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index 61081aaa3ff..522bbc4967e 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -7,6 +7,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy/ccpa" "net/http" "net/url" "regexp" @@ -21,6 +22,7 @@ type StrAdSeverParams struct { BidID string ConsentRequired bool ConsentString string + USPrivacySignal string InstantPlayCapable bool Iframe bool Height uint64 @@ -94,6 +96,11 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openr return nil, err } + usPolicySignal := "" + if usPolicy, err := ccpa.ReadPolicy(request); err == nil { + usPolicySignal = usPolicy.Value + } + return &adapters.RequestData{ Method: "POST", Uri: s.UriHelper.buildUri(StrAdSeverParams{ @@ -101,6 +108,7 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openr BidID: imp.ID, ConsentRequired: s.Util.gdprApplies(request), ConsentString: userInfo.Consent, + USPrivacySignal: usPolicySignal, Iframe: strImpParams.Iframe, Height: height, Width: width, @@ -184,6 +192,9 @@ func (h StrUriHelper) buildUri(params StrAdSeverParams) string { v.Set("bidId", params.BidID) v.Set("consent_required", fmt.Sprintf("%t", params.ConsentRequired)) v.Set("consent_string", params.ConsentString) + if params.USPrivacySignal != "" { + v.Set("us_privacy", params.USPrivacySignal) + } if params.TheTradeDeskUserId != "" { v.Set("ttduid", params.TheTradeDeskUserId) } diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index 40c59b50442..402e8365dd0 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -437,6 +437,7 @@ func TestBuildUri(t *testing.T) { BidID: "bid", ConsentRequired: true, ConsentString: "consent", + USPrivacySignal: "ccpa", InstantPlayCapable: true, Iframe: false, Height: 20, @@ -450,6 +451,7 @@ func TestBuildUri(t *testing.T) { "bidId=bid", "consent_required=true", "consent_string=consent", + "us_privacy=ccpa", "instant_play_capable=true", "stayInIframe=false", "height=20", diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index d1b2408ce66..5e0377ab27a 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -10,7 +10,7 @@ import ( ) const supplyId = "FGMrCMMc" -const strVersion = 7 +const strVersion = 8 func NewSharethroughBidder(endpoint string) *SharethroughAdapter { return &SharethroughAdapter{ From cd909915eeee8b1796243c4f5d1a8d5f46a6737b Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 6 May 2020 11:46:03 -0400 Subject: [PATCH 074/603] Remove Outdated GDPR AMP Special Case (#1283) --- exchange/utils.go | 3 +-- privacy/enforcement.go | 17 ++++--------- privacy/enforcement_test.go | 51 ++----------------------------------- 3 files changed, 8 insertions(+), 63 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index d1c95b88b86..f09b11513f1 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -43,7 +43,6 @@ func cleanOpenRTBRequests(ctx context.Context, gdpr := extractGDPR(orig, usersyncIfAmbiguous) consent := extractConsent(orig) - isAMP := labels.RType == pbsmetrics.ReqTypeAMP privacyEnforcement := privacy.Enforcement{ COPPA: orig.Regs != nil && orig.Regs.COPPA == 1, @@ -66,7 +65,7 @@ func cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.GDPR = false } - privacyEnforcement.Apply(bidReq, isAMP) + privacyEnforcement.Apply(bidReq) } return diff --git a/privacy/enforcement.go b/privacy/enforcement.go index caea396c0f6..592b6fb6937 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -17,14 +17,14 @@ func (e Enforcement) Any() bool { } // Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb.BidRequest, isAMP bool) { - e.apply(bidRequest, isAMP, NewScrubber()) +func (e Enforcement) Apply(bidRequest *openrtb.BidRequest) { + e.apply(bidRequest, NewScrubber()) } -func (e Enforcement) apply(bidRequest *openrtb.BidRequest, isAMP bool, scrubber Scrubber) { +func (e Enforcement) apply(bidRequest *openrtb.BidRequest, scrubber Scrubber) { if bidRequest != nil && e.Any() { bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceMacAndIFA(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) - bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(isAMP), e.getGeoScrubStrategy()) + bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) } } @@ -56,18 +56,11 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { return ScrubStrategyGeoNone } -func (e Enforcement) getUserScrubStrategy(isAMP bool) ScrubStrategyUser { +func (e Enforcement) getUserScrubStrategy() ScrubStrategyUser { if e.COPPA { return ScrubStrategyUserFull } - // There's no way for AMP to send a GDPR consent string yet so it's hard - // to know if the vendor is consented or not and therefore for AMP requests - // we keep the BuyerUID as is for GDPR. - if e.GDPR && isAMP { - return ScrubStrategyUserNone - } - if e.GDPR || e.CCPA { return ScrubStrategyUserBuyerIDOnly } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index ffc9da5d30c..3bc716b38d2 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -52,7 +52,6 @@ func TestAny(t *testing.T) { func TestApply(t *testing.T) { testCases := []struct { enforcement Enforcement - isAMP bool expectedDeviceMacAndIFA bool expectedDeviceIPv6 ScrubStrategyIPV6 expectedDeviceGeo ScrubStrategyGeo @@ -66,7 +65,6 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: true, }, - isAMP: true, expectedDeviceMacAndIFA: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, @@ -80,7 +78,6 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: false, }, - isAMP: false, expectedDeviceMacAndIFA: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, @@ -94,7 +91,6 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, }, - isAMP: false, expectedDeviceMacAndIFA: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, @@ -102,27 +98,12 @@ func TestApply(t *testing.T) { expectedUserGeo: ScrubStrategyGeoReducedPrecision, description: "GDPR", }, - { - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPR: true, - }, - isAMP: true, - expectedDeviceMacAndIFA: false, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - description: "GDPR For AMP", - }, { enforcement: Enforcement{ CCPA: true, COPPA: false, GDPR: false, }, - isAMP: false, expectedDeviceMacAndIFA: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, @@ -130,34 +111,6 @@ func TestApply(t *testing.T) { expectedUserGeo: ScrubStrategyGeoReducedPrecision, description: "CCPA", }, - { - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: false, - }, - isAMP: true, - expectedDeviceMacAndIFA: false, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserBuyerIDOnly, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - description: "CCPA For AMP", - }, - { - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: true, - }, - isAMP: true, - expectedDeviceMacAndIFA: false, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - description: "GDPR And CCPA For AMP", - }, } for _, test := range testCases { @@ -172,7 +125,7 @@ func TestApply(t *testing.T) { m.On("ScrubDevice", req.Device, test.expectedDeviceMacAndIFA, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(device).Once() m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(user).Once() - test.enforcement.apply(req, test.isAMP, m) + test.enforcement.apply(req, m) m.AssertExpectations(t) assert.Equal(t, device, req.Device, "Device Set Correctly") @@ -191,7 +144,7 @@ func TestApplyNoneApplicable(t *testing.T) { m := &mockScrubber{} - enforcement.apply(req, true, m) + enforcement.apply(req, m) m.AssertNotCalled(t, "ScrubDevice") m.AssertNotCalled(t, "ScrubUser") From cc3d2daa4a3a71161b153c912ebc621e3f5c0c17 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 6 May 2020 14:59:32 -0400 Subject: [PATCH 075/603] Stricter Privacy Scrubbing (#1286) * Stricter Privacy Scrubbing * Update Unit Test Style * Fixed Whitespace --- go.mod | 4 +- go.sum | 4 + privacy/enforcement.go | 18 +-- privacy/enforcement_test.go | 106 ++++++++--------- privacy/scrubber.go | 51 +++------ privacy/scrubber_test.go | 220 ++++++++++++++++++++++++++---------- 6 files changed, 244 insertions(+), 159 deletions(-) diff --git a/go.mod b/go.mod index 387b8b9815c..89cc69e4519 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect github.com/spf13/pflag v1.0.2 // indirect github.com/spf13/viper v1.1.0 - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.5.1 github.com/valyala/fasthttp v1.9.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect @@ -70,5 +70,5 @@ require ( golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect golang.org/x/text v0.3.0 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect - gopkg.in/yaml.v2 v2.2.1 + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index ad9caf5004b..f929408f0f3 100644 --- a/go.sum +++ b/go.sum @@ -143,6 +143,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw= @@ -196,3 +198,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 592b6fb6937..0230ca6b9af 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -23,15 +23,11 @@ func (e Enforcement) Apply(bidRequest *openrtb.BidRequest) { func (e Enforcement) apply(bidRequest *openrtb.BidRequest, scrubber Scrubber) { if bidRequest != nil && e.Any() { - bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceMacAndIFA(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) - bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) + bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) + bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getDemographicScrubStrategy(), e.getGeoScrubStrategy()) } } -func (e Enforcement) getDeviceMacAndIFA() bool { - return e.COPPA -} - func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { if e.COPPA { return ScrubStrategyIPV6Lowest32 @@ -56,14 +52,10 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { return ScrubStrategyGeoNone } -func (e Enforcement) getUserScrubStrategy() ScrubStrategyUser { +func (e Enforcement) getDemographicScrubStrategy() ScrubStrategyDemographic { if e.COPPA { - return ScrubStrategyUserFull - } - - if e.GDPR || e.CCPA { - return ScrubStrategyUserBuyerIDOnly + return ScrubStrategyDemographicAgeAndGender } - return ScrubStrategyUserNone + return ScrubStrategyDemographicNone } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 3bc716b38d2..c7433f8b271 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -15,31 +15,31 @@ func TestAny(t *testing.T) { description string }{ { + description: "All False", enforcement: Enforcement{ CCPA: false, COPPA: false, GDPR: false, }, - expected: false, - description: "All False", + expected: false, }, { + description: "All True", enforcement: Enforcement{ CCPA: true, COPPA: true, GDPR: true, }, - expected: true, - description: "All True", + expected: true, }, { + description: "Mixed", enforcement: Enforcement{ CCPA: false, COPPA: true, GDPR: false, }, - expected: true, - description: "Mixed", + expected: true, }, } @@ -51,117 +51,119 @@ func TestAny(t *testing.T) { func TestApply(t *testing.T) { testCases := []struct { + description string enforcement Enforcement - expectedDeviceMacAndIFA bool expectedDeviceIPv6 ScrubStrategyIPV6 expectedDeviceGeo ScrubStrategyGeo - expectedUser ScrubStrategyUser + expectedUserDemographic ScrubStrategyDemographic expectedUserGeo ScrubStrategyGeo - description string }{ { + description: "All Enforced", enforcement: Enforcement{ CCPA: true, COPPA: true, GDPR: true, }, - expectedDeviceMacAndIFA: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserFull, + expectedUserDemographic: ScrubStrategyDemographicAgeAndGender, expectedUserGeo: ScrubStrategyGeoFull, - description: "All Enforced - Most Strict", }, { + description: "CCPA Only", + enforcement: Enforcement{ + CCPA: true, + COPPA: false, + GDPR: false, + }, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedUserDemographic: ScrubStrategyDemographicNone, + expectedUserGeo: ScrubStrategyGeoReducedPrecision, + }, + { + description: "COPPA Only", enforcement: Enforcement{ CCPA: false, COPPA: true, GDPR: false, }, - expectedDeviceMacAndIFA: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserFull, + expectedUserDemographic: ScrubStrategyDemographicAgeAndGender, expectedUserGeo: ScrubStrategyGeoFull, - description: "COPPA", }, { + description: "GDPR Only", enforcement: Enforcement{ CCPA: false, COPPA: false, GDPR: true, }, - expectedDeviceMacAndIFA: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserBuyerIDOnly, + expectedUserDemographic: ScrubStrategyDemographicNone, expectedUserGeo: ScrubStrategyGeoReducedPrecision, - description: "GDPR", - }, - { - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: false, - }, - expectedDeviceMacAndIFA: false, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserBuyerIDOnly, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - description: "CCPA", }, } for _, test := range testCases { req := &openrtb.BidRequest{ - Device: &openrtb.Device{DIDSHA1: "before"}, - User: &openrtb.User{ID: "before"}, + Device: &openrtb.Device{}, + User: &openrtb.User{}, } - device := &openrtb.Device{DIDSHA1: "after"} - user := &openrtb.User{ID: "after"} + replacedDevice := &openrtb.Device{} + replacedUser := &openrtb.User{} m := &mockScrubber{} - m.On("ScrubDevice", req.Device, test.expectedDeviceMacAndIFA, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(device).Once() - m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(user).Once() + m.On("ScrubDevice", req.Device, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() + m.On("ScrubUser", req.User, test.expectedUserDemographic, test.expectedUserGeo).Return(replacedUser).Once() test.enforcement.apply(req, m) m.AssertExpectations(t) - assert.Equal(t, device, req.Device, "Device Set Correctly") - assert.Equal(t, user, req.User, "User Set Correctly") + assert.Same(t, replacedDevice, req.Device, "Device") + assert.Same(t, replacedUser, req.User, "User") } } func TestApplyNoneApplicable(t *testing.T) { - enforcement := Enforcement{} - device := &openrtb.Device{DIDSHA1: "original"} - user := &openrtb.User{ID: "original"} - req := &openrtb.BidRequest{ - Device: device, - User: user, - } + req := &openrtb.BidRequest{} m := &mockScrubber{} + enforcement := Enforcement{ + CCPA: false, + COPPA: false, + GDPR: false, + } enforcement.apply(req, m) m.AssertNotCalled(t, "ScrubDevice") m.AssertNotCalled(t, "ScrubUser") - assert.Equal(t, device, req.Device, "Device Set Correctly") - assert.Equal(t, user, req.User, "User Set Correctly") +} + +func TestApplyNil(t *testing.T) { + m := &mockScrubber{} + + enforcement := Enforcement{} + enforcement.apply(nil, m) + + m.AssertNotCalled(t, "ScrubDevice") + m.AssertNotCalled(t, "ScrubUser") } type mockScrubber struct { mock.Mock } -func (m *mockScrubber) ScrubDevice(device *openrtb.Device, macAndIFA bool, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { - args := m.Called(device, macAndIFA, ipv6, geo) +func (m *mockScrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { + args := m.Called(device, ipv6, geo) return args.Get(0).(*openrtb.Device) } -func (m *mockScrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { - args := m.Called(user, strategy, geo) +func (m *mockScrubber) ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User { + args := m.Called(user, demographic, geo) return args.Get(0).(*openrtb.User) } diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 916b660dcc5..45b79c20a4e 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -34,24 +34,21 @@ const ( ScrubStrategyGeoReducedPrecision ) -// ScrubStrategyUser defines the approach to scrub PII from user data. -type ScrubStrategyUser int +// ScrubStrategyDemographic defines the approach to non-location demographic data. +type ScrubStrategyDemographic int const ( - // ScrubStrategyUserNone does not remove user data. - ScrubStrategyUserNone ScrubStrategyUser = iota + // ScrubStrategyDemographicNone does not remove non-location demographic data. + ScrubStrategyDemographicNone ScrubStrategyDemographic = iota - // ScrubStrategyUserFull removes the user's buyer id, exchange id year of birth, and gender. - ScrubStrategyUserFull - - // ScrubStrategyUserBuyerIDOnly removes the user's buyer id. - ScrubStrategyUserBuyerIDOnly + // ScrubStrategyDemographicAgeAndGender removes age and gender data. + ScrubStrategyDemographicAgeAndGender ) // Scrubber removes PII from parts of an OpenRTB request. type Scrubber interface { - ScrubDevice(device *openrtb.Device, macAndIFA bool, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device - ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User + ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device + ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User } type scrubber struct{} @@ -61,25 +58,21 @@ func NewScrubber() Scrubber { return scrubber{} } -func (scrubber) ScrubDevice(device *openrtb.Device, macAndIFA bool, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +func (scrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { if device == nil { return nil } deviceCopy := *device - deviceCopy.DIDMD5 = "" deviceCopy.DIDSHA1 = "" deviceCopy.DPIDMD5 = "" deviceCopy.DPIDSHA1 = "" + deviceCopy.IFA = "" + deviceCopy.MACMD5 = "" + deviceCopy.MACSHA1 = "" deviceCopy.IP = scrubIPV4(device.IP) - if macAndIFA { - deviceCopy.MACSHA1 = "" - deviceCopy.MACMD5 = "" - deviceCopy.IFA = "" - } - switch ipv6 { case ScrubStrategyIPV6Lowest16: deviceCopy.IPv6 = scrubIPV6Lowest16Bits(device.IPv6) @@ -97,21 +90,19 @@ func (scrubber) ScrubDevice(device *openrtb.Device, macAndIFA bool, ipv6 ScrubSt return &deviceCopy } -func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { +func (scrubber) ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User { if user == nil { return nil } userCopy := *user + userCopy.BuyerUID = "" + userCopy.ID = "" - switch strategy { - case ScrubStrategyUserFull: - userCopy.BuyerUID = "" - userCopy.ID = "" + switch demographic { + case ScrubStrategyDemographicAgeAndGender: userCopy.Yob = 0 userCopy.Gender = "" - case ScrubStrategyUserBuyerIDOnly: - userCopy.BuyerUID = "" } switch geo { @@ -169,13 +160,7 @@ func scrubGeoFull(geo *openrtb.Geo) *openrtb.Geo { return nil } - geoCopy := *geo - geoCopy.Lat = 0 - geoCopy.Lon = 0 - geoCopy.Metro = "" - geoCopy.City = "" - geoCopy.ZIP = "" - return &geoCopy + return &openrtb.Geo{} } func scrubGeoPrecision(geo *openrtb.Geo) *openrtb.Geo { diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 168fb5fb23e..2d5ee667538 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -28,13 +28,13 @@ func TestScrubDevice(t *testing.T) { } testCases := []struct { + description string expected *openrtb.Device - isMacAndIFA bool ipv6 ScrubStrategyIPV6 geo ScrubStrategyGeo - description string }{ { + description: "IPv6 Lowest 32 & Geo Full", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -47,12 +47,11 @@ func TestScrubDevice(t *testing.T) { IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", Geo: &openrtb.Geo{}, }, - isMacAndIFA: true, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoFull, - description: "Full Scrubbing", + ipv6: ScrubStrategyIPV6Lowest32, + geo: ScrubStrategyGeoFull, }, { + description: "IPv6 Lowest 16 & Geo Full", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -65,12 +64,11 @@ func TestScrubDevice(t *testing.T) { IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", Geo: &openrtb.Geo{}, }, - isMacAndIFA: true, - ipv6: ScrubStrategyIPV6Lowest16, - geo: ScrubStrategyGeoFull, - description: "IPv6 Lowest 16", + ipv6: ScrubStrategyIPV6Lowest16, + geo: ScrubStrategyGeoFull, }, { + description: "IPv6 None & Geo Full", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -83,12 +81,11 @@ func TestScrubDevice(t *testing.T) { IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", Geo: &openrtb.Geo{}, }, - isMacAndIFA: true, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoFull, - description: "IPv6 None", + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoFull, }, { + description: "IPv6 Lowest 32 & Geo Reduced", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -107,12 +104,57 @@ func TestScrubDevice(t *testing.T) { ZIP: "some zip", }, }, - isMacAndIFA: true, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoReducedPrecision, - description: "Geo Reduced Precision", + ipv6: ScrubStrategyIPV6Lowest32, + geo: ScrubStrategyGeoReducedPrecision, + }, + { + description: "IPv6 Lowest 16 & Geo Reduced", + expected: &openrtb.Device{ + DIDMD5: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACSHA1: "", + MACMD5: "", + IFA: "", + IP: "1.2.3.0", + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", + Geo: &openrtb.Geo{ + Lat: 123.46, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + }, + ipv6: ScrubStrategyIPV6Lowest16, + geo: ScrubStrategyGeoReducedPrecision, + }, + { + description: "IPv6 None & Geo Reduced", + expected: &openrtb.Device{ + DIDMD5: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACSHA1: "", + MACMD5: "", + IFA: "", + IP: "1.2.3.0", + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + Geo: &openrtb.Geo{ + Lat: 123.46, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + }, + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoReducedPrecision, }, { + description: "IPv6 Lowest 32 & Geo None", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -131,41 +173,72 @@ func TestScrubDevice(t *testing.T) { ZIP: "some zip", }, }, - isMacAndIFA: true, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoNone, - description: "Geo None", + ipv6: ScrubStrategyIPV6Lowest32, + geo: ScrubStrategyGeoNone, }, { + description: "IPv6 Lowest 16 & Geo None", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", DPIDMD5: "", DPIDSHA1: "", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", + MACSHA1: "", + MACMD5: "", + IFA: "", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{}, + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", + Geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, }, - isMacAndIFA: false, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoFull, - description: "Without MAC Address And IFA Scrubbing", + ipv6: ScrubStrategyIPV6Lowest16, + geo: ScrubStrategyGeoNone, + }, + { + description: "IPv6 None & Geo None", + expected: &openrtb.Device{ + DIDMD5: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACSHA1: "", + MACMD5: "", + IFA: "", + IP: "1.2.3.0", + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + Geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + }, + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoNone, }, } for _, test := range testCases { - result := NewScrubber().ScrubDevice(device, test.isMacAndIFA, test.ipv6, test.geo) + result := NewScrubber().ScrubDevice(device, test.ipv6, test.geo) assert.Equal(t, test.expected, result, test.description) } } +func TestScrubDeviceNil(t *testing.T) { + result := NewScrubber().ScrubDevice(nil, ScrubStrategyIPV6None, ScrubStrategyGeoNone) + assert.Nil(t, result) +} + func TestScrubUser(t *testing.T) { user := &openrtb.User{ - BuyerUID: "anyBuyerUID", ID: "anyID", + BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Geo: &openrtb.Geo{ @@ -179,52 +252,77 @@ func TestScrubUser(t *testing.T) { testCases := []struct { expected *openrtb.User - strategy ScrubStrategyUser + demographic ScrubStrategyDemographic geo ScrubStrategyGeo description string }{ { + description: "Demographic Age And Gender & Geo Full", expected: &openrtb.User{ - BuyerUID: "", ID: "", + BuyerUID: "", Yob: 0, Gender: "", Geo: &openrtb.Geo{}, }, - strategy: ScrubStrategyUserFull, + demographic: ScrubStrategyDemographicAgeAndGender, geo: ScrubStrategyGeoFull, - description: "Full Scrubbing", }, { + description: "Demographic Age And Gender & Geo Reduced", expected: &openrtb.User{ + ID: "", BuyerUID: "", - ID: "anyID", - Yob: 42, - Gender: "anyGender", - Geo: &openrtb.Geo{}, + Yob: 0, + Gender: "", + Geo: &openrtb.Geo{ + Lat: 123.46, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, }, - strategy: ScrubStrategyUserBuyerIDOnly, - geo: ScrubStrategyGeoFull, - description: "User Buyer ID Only", + demographic: ScrubStrategyDemographicAgeAndGender, + geo: ScrubStrategyGeoReducedPrecision, + }, + { + description: "Demographic Age And Gender & Geo None", + expected: &openrtb.User{ + ID: "", + BuyerUID: "", + Yob: 0, + Gender: "", + Geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + }, + demographic: ScrubStrategyDemographicAgeAndGender, + geo: ScrubStrategyGeoNone, }, { + description: "Demographic None & Geo Full", expected: &openrtb.User{ - BuyerUID: "anyBuyerUID", - ID: "anyID", + ID: "", + BuyerUID: "", Yob: 42, Gender: "anyGender", Geo: &openrtb.Geo{}, }, - strategy: ScrubStrategyUserNone, + demographic: ScrubStrategyDemographicNone, geo: ScrubStrategyGeoFull, - description: "User None", }, { + description: "Demographic None & Geo Reduced", expected: &openrtb.User{ - BuyerUID: "", ID: "", - Yob: 0, - Gender: "", + BuyerUID: "", + Yob: 42, + Gender: "anyGender", Geo: &openrtb.Geo{ Lat: 123.46, Lon: 678.89, @@ -233,16 +331,16 @@ func TestScrubUser(t *testing.T) { ZIP: "some zip", }, }, - strategy: ScrubStrategyUserFull, + demographic: ScrubStrategyDemographicNone, geo: ScrubStrategyGeoReducedPrecision, - description: "Geo Reduced Precision", }, { + description: "Demographic None & Geo None", expected: &openrtb.User{ - BuyerUID: "", ID: "", - Yob: 0, - Gender: "", + BuyerUID: "", + Yob: 42, + Gender: "anyGender", Geo: &openrtb.Geo{ Lat: 123.456, Lon: 678.89, @@ -251,18 +349,22 @@ func TestScrubUser(t *testing.T) { ZIP: "some zip", }, }, - strategy: ScrubStrategyUserFull, + demographic: ScrubStrategyDemographicNone, geo: ScrubStrategyGeoNone, - description: "Geo None", }, } for _, test := range testCases { - result := NewScrubber().ScrubUser(user, test.strategy, test.geo) + result := NewScrubber().ScrubUser(user, test.demographic, test.geo) assert.Equal(t, test.expected, result, test.description) } } +func TestScrubUserNil(t *testing.T) { + result := NewScrubber().ScrubUser(nil, ScrubStrategyDemographicNone, ScrubStrategyGeoNone) + assert.Nil(t, result) +} + func TestScrubIPV4(t *testing.T) { testCases := []struct { IP string From 2481fea01f9b339f5455d93a370e2550c58ac087 Mon Sep 17 00:00:00 2001 From: Arne Schulz Date: Mon, 11 May 2020 17:09:33 +0200 Subject: [PATCH 076/603] Add Adapter Orbidder (#1275) Co-authored-by: Volk, Rainer Co-authored-by: RainerVolk4014 <53347752+RainerVolk4014@users.noreply.github.com> Co-authored-by: rvolk <> Co-authored-by: Hendrik Iseke Co-authored-by: hendrikiseke1979 <53309111+hendrikiseke1979@users.noreply.github.com> --- adapters/orbidder/orbidder.go | 127 ++++++++++++++++++ adapters/orbidder/orbidder_test.go | 25 ++++ .../exemplary/simple-app-banner.json | 111 +++++++++++++++ .../orbiddertest/params/race/banner.json | 5 + .../supplemental/dsp-bad-request-example.json | 78 +++++++++++ .../dsp-bad-response-example.json | 78 +++++++++++ .../dsp-internal-server-error-example.json | 78 +++++++++++ .../dsp-invalid-accountid-example.json | 78 +++++++++++ .../supplemental/empty-imp-request-error.json | 19 +++ .../supplemental/ext-unmarshall-error.json | 32 +++++ .../supplemental/no-content-response.json | 73 ++++++++++ .../supplemental/valid-and-invalid-imps.json | 123 +++++++++++++++++ adapters/orbidder/params_test.go | 65 +++++++++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_orbidder.go | 8 ++ static/bidder-info/orbidder.yaml | 9 ++ static/bidder-params/orbidder.json | 24 ++++ usersync/usersyncers/syncer_test.go | 1 + 20 files changed, 939 insertions(+) create mode 100644 adapters/orbidder/orbidder.go create mode 100644 adapters/orbidder/orbidder_test.go create mode 100644 adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json create mode 100644 adapters/orbidder/orbiddertest/params/race/banner.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/dsp-bad-request-example.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/dsp-bad-response-example.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/dsp-internal-server-error-example.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/dsp-invalid-accountid-example.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/empty-imp-request-error.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/ext-unmarshall-error.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/no-content-response.json create mode 100644 adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json create mode 100644 adapters/orbidder/params_test.go create mode 100644 openrtb_ext/imp_orbidder.go create mode 100644 static/bidder-info/orbidder.yaml create mode 100644 static/bidder-params/orbidder.json diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go new file mode 100644 index 00000000000..27b41c89857 --- /dev/null +++ b/adapters/orbidder/orbidder.go @@ -0,0 +1,127 @@ +package orbidder + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type OrbidderAdapter struct { + endpoint string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids from orbidder. +func (rcv *OrbidderAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var validImps []openrtb.Imp + + // check if imps exists, if not return error and do send request to orbidder. + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impressions in request", + }} + } + + // validate imps + for _, imp := range request.Imp { + if err := preprocess(&imp); err != nil { + errs = append(errs, err) + continue + } + validImps = append(validImps, imp) + } + + if len(validImps) == 0 { + return nil, errs + } + + //set imp array to only valid imps + request.Imp = validImps + + requestBodyJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: rcv.endpoint, + Body: requestBodyJSON, + Headers: headers, + }}, errs +} + +func preprocess(imp *openrtb.Imp) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + var orbidderExt openrtb_ext.ExtImpOrbidder + if err := json.Unmarshal(bidderExt.Bidder, &orbidderExt); err != nil { + return &errortypes.BadInput{ + Message: "Wrong orbidder bidder ext: " + err.Error(), + } + } + + return nil +} + +// MakeBids unpacks server response into Bids. +func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode >= http.StatusInternalServerError { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Dsp server internal error.", response.StatusCode), + }} + } + + if response.StatusCode >= http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Bad request to dsp.", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Bad response from dsp.", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + return bidResponse, nil +} + +func NewOrbidderBidder(endpoint string) *OrbidderAdapter { + return &OrbidderAdapter{ + endpoint: endpoint, + } +} diff --git a/adapters/orbidder/orbidder_test.go b/adapters/orbidder/orbidder_test.go new file mode 100644 index 00000000000..e0f7a6b4265 --- /dev/null +++ b/adapters/orbidder/orbidder_test.go @@ -0,0 +1,25 @@ +package orbidder + +import ( + "encoding/json" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUnmarshalOrbidderExtImp(t *testing.T) { + ext := json.RawMessage(`{"accountId":"orbidder-test", "placementId":"center-banner", "bidfloor": 0.1}`) + impExt := new(openrtb_ext.ExtImpOrbidder) + + assert.NoError(t, json.Unmarshal(ext, impExt)) + assert.Equal(t, &openrtb_ext.ExtImpOrbidder{ + AccountId: "orbidder-test", + PlacementId: "center-banner", + BidFloor: 0.1, + }, impExt) +} + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "orbiddertest", NewOrbidderBidder("https://orbidder-test")) +} diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json new file mode 100644 index 00000000000..8697bff3a92 --- /dev/null +++ b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/orbidder/orbiddertest/params/race/banner.json b/adapters/orbidder/orbiddertest/params/race/banner.json new file mode 100644 index 00000000000..bdb0e010e05 --- /dev/null +++ b/adapters/orbidder/orbiddertest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "accountId": "orbidder-test", + "placementId": "center-banner", + "bidfloor": 0.1 +} \ No newline at end of file diff --git a/adapters/orbidder/orbiddertest/supplemental/dsp-bad-request-example.json b/adapters/orbidder/orbiddertest/supplemental/dsp-bad-request-example.json new file mode 100644 index 00000000000..69496c4ff3f --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/dsp-bad-request-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request to dsp.", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/dsp-bad-response-example.json b/adapters/orbidder/orbiddertest/supplemental/dsp-bad-response-example.json new file mode 100644 index 00000000000..d1c57a54a9e --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/dsp-bad-response-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 300, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 300. Bad response from dsp.", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/dsp-internal-server-error-example.json b/adapters/orbidder/orbiddertest/supplemental/dsp-internal-server-error-example.json new file mode 100644 index 00000000000..20ea36ab38c --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/dsp-internal-server-error-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Dsp server internal error.", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/dsp-invalid-accountid-example.json b/adapters/orbidder/orbiddertest/supplemental/dsp-invalid-accountid-example.json new file mode 100644 index 00000000000..6bc0482dd0c --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/dsp-invalid-accountid-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 403, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Bad request to dsp.", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/empty-imp-request-error.json b/adapters/orbidder/orbiddertest/supplemental/empty-imp-request-error.json new file mode 100644 index 00000000000..0c5cf6d2faa --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/empty-imp-request-error.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + ], + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impressions in request", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/ext-unmarshall-error.json b/adapters/orbidder/orbiddertest/supplemental/ext-unmarshall-error.json new file mode 100644 index 00000000000..447e2985f92 --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/ext-unmarshall-error.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "ext": { + "bidder": { + "accountId": [] + } + } + } + ], + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Wrong orbidder bidder ext: json: cannot unmarshal array into Go struct field ExtImpOrbidder.accountId of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/no-content-response.json b/adapters/orbidder/orbiddertest/supplemental/no-content-response.json new file mode 100644 index 00000000000..f3b1b287da7 --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/no-content-response.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json new file mode 100644 index 00000000000..b6db9f48ee3 --- /dev/null +++ b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + }, + { + "id": "test-imp-id-INVALID", + "banner": { + "format": [{}] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/orbidder/params_test.go b/adapters/orbidder/params_test.go new file mode 100644 index 00000000000..19c4ed8d9d4 --- /dev/null +++ b/adapters/orbidder/params_test.go @@ -0,0 +1,65 @@ +package orbidder + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +// This file actually intends to test static/bidder-params/orbidder.json +// +// These also validate the format of the external API: request.imp[i].ext.orbidder + +// TestValidParams makes sure that the orbidder schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderOrbidder, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected orbidder params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the orbidder schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderOrbidder, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId":"123","accountId":"orbidder-test"}`, + `{"placementId":"123","accountId":"orbidder-test","bidfloor":0.5}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"placement_id":"123"}`, + `{"placementId":123}`, + `{"placementId":"123"}`, + `{"account_id":"orbidder-test"}`, + `{"accountId":123}`, + `{"accountId":"orbidder-test"}`, + `{"placementId":123,"account_id":"orbidder-test"}`, + `{"placementId":"123","account_id":123}`, + `{"placementId":"123","accountId":"orbidder-test","bidfloor":"0.5"}`, + `{"placementId":"123","bidfloor":"0.5"}`, + `{"accountId":"orbidder-test","bidfloor":"0.5"}`, +} diff --git a/config/config.go b/config/config.go index a7132edbc81..ccdb5c0625b 100755 --- a/config/config.go +++ b/config/config.go @@ -729,6 +729,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") + v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 17814b3639a..2029d1a7553 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -46,6 +46,7 @@ import ( "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/openx" + "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -119,6 +120,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), openrtb_ext.BidderNanoInteractive: nanointeractive.NewNanoIneractiveBidder(cfg.Adapters[string(openrtb_ext.BidderNanoInteractive)].Endpoint), openrtb_ext.BidderNinthDecimal: ninthdecimal.NewNinthDecimalBidder(cfg.Adapters[string(openrtb_ext.BidderNinthDecimal)].Endpoint), + openrtb_ext.BidderOrbidder: orbidder.NewOrbidderBidder(cfg.Adapters[string(openrtb_ext.BidderOrbidder)].Endpoint), openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c9b7f7a0519..01112091cdf 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -64,6 +64,7 @@ const ( BidderNanoInteractive BidderName = "nanointeractive" BidderNinthDecimal BidderName = "ninthdecimal" BidderOpenx BidderName = "openx" + BidderOrbidder BidderName = "orbidder" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -134,6 +135,7 @@ var BidderMap = map[string]BidderName{ "nanointeractive": BidderNanoInteractive, "ninthdecimal": BidderNinthDecimal, "openx": BidderOpenx, + "orbidder": BidderOrbidder, "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, "pulsepoint": BidderPulsepoint, diff --git a/openrtb_ext/imp_orbidder.go b/openrtb_ext/imp_orbidder.go new file mode 100644 index 00000000000..ad141bdbcdf --- /dev/null +++ b/openrtb_ext/imp_orbidder.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpOrbidder defines the contract for bidrequest.imp[i].ext.openx +type ExtImpOrbidder struct { + AccountId string `json:"accountId"` + PlacementId string `json:"placementId"` + BidFloor float64 `json:"bidfloor"` +} diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml new file mode 100644 index 00000000000..c683087d197 --- /dev/null +++ b/static/bidder-info/orbidder.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "realtime-siggi@otto.de" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-params/orbidder.json b/static/bidder-params/orbidder.json new file mode 100644 index 00000000000..d986b23284e --- /dev/null +++ b/static/bidder-params/orbidder.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Orbidder Adapter Params", + "description": "A schema which validates params accepted by the Orbidder adapter", + + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "The marketer's accountId." + }, + "placementId": { + "type": "string", + "description": "The placementId of the ad unit." + }, + "bidfloor": { + "type": "number", + "description": "The minimum CPM price in EUR.", + "minimum": 0 + } + }, + + "required": ["accountId", "placementId"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 44ff15bd5fe..88c1b9467a6 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -83,6 +83,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderKubient: true, openrtb_ext.BidderPubnative: true, openrtb_ext.BidderKidoz: true, + openrtb_ext.BidderOrbidder: true, } for bidder, config := range cfg.Adapters { From 4257bf1ed9dc1dd2647c4f463890b95a775147ab Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Tue, 12 May 2020 07:27:03 -0700 Subject: [PATCH 077/603] Added OpenX Bidder adapter documentation (#1291) --- docs/bidders/openx.md | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/bidders/openx.md diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md new file mode 100644 index 00000000000..c366db3ab61 --- /dev/null +++ b/docs/bidders/openx.md @@ -0,0 +1,62 @@ +# OpenX Bidder + +OpenX supports the following parameters: + +| property | type | required? | description | example | +|----------|------|-----------|-------------|---------| +| unit | string | required | The ad unit id | "10092842" | +| delDomain | string | required | The delivery domain for the customer | "sademo-d.openx.net" | +| customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | +| customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | + +If you have any questions regarding setting up, please reach out to your account manager or + + +## Test Request + +### App Impression Object +``` +{ + "id": "test-impression-id", + "banner": { + "format": [ + { + "w": 480, + "h": 300 + }, + { + "w": 480, + "h": 320 + } + ] + }, + "ext": { + "openx": { + "delDomain": "mobile-d.openx.net", + "unit": "541028953" + } + } +} +``` + + +### Web +``` +{ + "id": "div1", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "openx": { + "unit": "540949380", + "delDomain": "sademo-d.openx.net" + }, + } +} +``` \ No newline at end of file From 93b8a0edb8e418f4e360469d6d136995ddf45dc1 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Wed, 13 May 2020 10:12:14 -0700 Subject: [PATCH 078/603] OpenX adapter: Pass rewarded video flag (#1290) --- adapters/openx/openx.go | 8 ++ .../openxtest/exemplary/video-rewarded.json | 102 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 adapters/openx/openxtest/exemplary/video-rewarded.json diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index 63e8e697869..63297d0a4ee 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -142,6 +142,14 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { } } + if imp.Video != nil { + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { + imp.Video.Ext = json.RawMessage(`{"rewarded":1}`) + } else { + imp.Video.Ext = nil + } + } + return nil } diff --git a/adapters/openx/openxtest/exemplary/video-rewarded.json b/adapters/openx/openxtest/exemplary/video-rewarded.json new file mode 100644 index 00000000000..b16a92f23ac --- /dev/null +++ b/adapters/openx/openxtest/exemplary/video-rewarded.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "bidder": { + "unit": "539439964", + "delDomain": "se-demo-d.openx.net" + }, + "prebid": { + "is_rewarded_inventory": 1 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.openx.net/prebid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576, + "ext": { + "rewarded": 1 + } + }, + "tagid": "539439964", + "instl": 1 + } + ], + "ext": { + "bc": "hb_pbs_1.0.0", + "delDomain": "se-demo-d.openx.net" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "openx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} From c18a2d86c5bad5987008b3df485072ebf471683e Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 14 May 2020 07:35:49 -0700 Subject: [PATCH 079/603] Bugfix for missing fields in imp.video (#1297) Co-authored-by: Veronika Solovei --- endpoints/openrtb2/video_auction.go | 8 +++----- endpoints/openrtb2/video_auction_test.go | 25 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index c7316604d73..cf764bc9d2d 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -381,11 +381,9 @@ func max(a, b int) int { } func createImpressionTemplate(imp openrtb.Imp, video *openrtb.Video) openrtb.Imp { - imp.Video = &openrtb.Video{} - imp.Video.W = video.W - imp.Video.H = video.H - imp.Video.Protocols = video.Protocols - imp.Video.MIMEs = video.MIMEs + //for every new impression we need to have it's own copy of video object, because we customize it in further processing + newVideo := *video + imp.Video = &newVideo return imp } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index ec525c6ff08..38c9dc3f685 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1005,6 +1005,31 @@ func TestHandleErrorDebugLog(t *testing.T) { assert.NotEmpty(t, debugLog.CacheKey, "DebugLog CacheKey value should have been set") } +func TestCreateImpressionTemplate(t *testing.T) { + + imp := openrtb.Imp{} + imp.Video = &openrtb.Video{} + imp.Video.Protocols = []openrtb.Protocol{1, 2} + imp.Video.MIMEs = []string{"video/mp4"} + imp.Video.H = 200 + imp.Video.W = 400 + imp.Video.PlaybackMethod = []openrtb.PlaybackMethod{5, 6} + + video := openrtb.Video{} + video.Protocols = []openrtb.Protocol{3, 4} + video.MIMEs = []string{"video/flv"} + video.H = 300 + video.W = 0 + video.PlaybackMethod = []openrtb.PlaybackMethod{7, 8} + + res := createImpressionTemplate(imp, &video) + assert.Equal(t, res.Video.Protocols, []openrtb.Protocol{3, 4}, "Incorrect video protocols") + assert.Equal(t, res.Video.MIMEs, []string{"video/flv"}, "Incorrect video MIMEs") + assert.Equal(t, int(res.Video.H), 300, "Incorrect video height") + assert.Equal(t, int(res.Video.W), 0, "Incorrect video width") + assert.Equal(t, res.Video.PlaybackMethod, []openrtb.PlaybackMethod{7, 8}, "Incorrect video playback method") +} + func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} From 9f7ed209b685c3135eee56a64f963476268716a9 Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Thu, 14 May 2020 17:44:06 +0300 Subject: [PATCH 080/603] Add cpmOverride (#1289) * Add cpmOverride Enabled `request.ext.rubicon.debug.cpmOverride` and `request.imp[].ext.rubicon.debug.cpmOverride` processing. Updates tests * Remove unnecessary error checks and add shallow copy * Fixed same pointer --- adapters/rubicon/rubicon.go | 71 ++++++++++++++++++++--- adapters/rubicon/rubicon_test.go | 97 +++++++++++++++++++++++++++++++- openrtb_ext/imp_rubicon.go | 6 ++ 3 files changed, 163 insertions(+), 11 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index dad85ee1184..ee737bd05ea 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -48,6 +48,18 @@ type rubiconParams struct { Video rubiconVideoParams `json:"video"` } +type bidRequestExt struct { + Rubicon bidRequestExtRubicon `json:"rubicon,omitempty"` +} + +type bidRequestExtRubicon struct { + Debug bidRequestExtRubiconDebug `json:"debug,omitempty"` +} + +type bidRequestExtRubiconDebug struct { + CpmOverride float64 `json:"cpmOverride,omitempty"` +} + type rubiconImpExtRPTrack struct { Mint string `json:"mint"` MintVersion string `json:"mint_version"` @@ -578,6 +590,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap requestImpCopy := request.Imp + rubiconRequest := *request for i := 0; i < numRequests; i++ { thisImp := requestImpCopy[i] @@ -677,14 +690,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap errs = append(errs, err) continue } - request.User = &userCopy + rubiconRequest.User = &userCopy } if request.Device != nil { deviceCopy := *request.Device deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: request.Device.PxRatio}} deviceCopy.Ext, err = json.Marshal(&deviceExt) - request.Device = &deviceCopy + rubiconRequest.Device = &deviceCopy } isVideo := isVideo(thisImp) @@ -732,28 +745,28 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap siteCopy.Ext, err = json.Marshal(&siteExt) siteCopy.Publisher = &openrtb.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - request.Site = &siteCopy + rubiconRequest.Site = &siteCopy } if request.App != nil { appCopy := *request.App appCopy.Ext, err = json.Marshal(&siteExt) appCopy.Publisher = &openrtb.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) - request.App = &appCopy + rubiconRequest.App = &appCopy } reqBadv := request.BAdv if reqBadv != nil { if len(reqBadv) > badvLimitSize { - request.BAdv = reqBadv[:badvLimitSize] + rubiconRequest.BAdv = reqBadv[:badvLimitSize] } } - request.Imp = []openrtb.Imp{thisImp} - request.Cur = nil - request.Ext = nil + rubiconRequest.Imp = []openrtb.Imp{thisImp} + rubiconRequest.Cur = nil + rubiconRequest.Ext = nil - reqJSON, err := json.Marshal(request) + reqJSON, err := json.Marshal(rubiconRequest) if err != nil { errs = append(errs, err) continue @@ -900,9 +913,22 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR bidType = openrtb_ext.BidTypeVideo } + impToCpmOverride := mapImpIdToCpmOverride(internalRequest.Imp) + cmpOverride := cmpOverrideFromBidRequest(internalRequest) + for _, sb := range bidResp.SeatBid { for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] + + bidCmpOverride, ok := impToCpmOverride[bid.ImpID] + if !ok || bidCmpOverride == 0 { + bidCmpOverride = cmpOverride + } + + if bidCmpOverride > 0 { + bid.Price = bidCmpOverride + } + if bid.Price != 0 { // Since Rubicon XAPI returns only one bid per response // copy response.bidid to openrtb_response.seatbid.bid.bidid @@ -919,3 +945,30 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, nil } + +func cmpOverrideFromBidRequest(bidRequest *openrtb.BidRequest) float64 { + var bidRequestExt bidRequestExt + if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { + return 0 + } + + return bidRequestExt.Rubicon.Debug.CpmOverride +} + +func mapImpIdToCpmOverride(imps []openrtb.Imp) map[string]float64 { + impIdToCmpOverride := make(map[string]float64) + for _, imp := range imps { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + continue + } + + var rubiconExt openrtb_ext.ExtImpRubicon + if err := json.Unmarshal(bidderExt.Bidder, &rubiconExt); err != nil { + continue + } + + impIdToCmpOverride[imp.ID] = rubiconExt.Debug.CpmOverride + } + return impIdToCmpOverride +} diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 96623659d08..7a2cc28896b 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -973,9 +973,9 @@ func TestOpenRTBRequest(t *testing.T) { } assert.Equal(t, request.ID, rpRequest.ID, "Bad Request ID. Expected %s, Got %s", request.ID, rpRequest.ID) - assert.Equal(t, len(request.Imp), len(rpRequest.Imp), "Wrong len(request.Imp). Expected %d, Got %d", len(request.Imp), len(rpRequest.Imp)) + assert.Equal(t, 1, len(rpRequest.Imp), "Wrong len(request.Imp). Expected %d, Got %d", len(request.Imp), len(rpRequest.Imp)) assert.Nil(t, rpRequest.Cur, "Wrong request.Cur. Expected nil, Got %s", rpRequest.Cur) - assert.Nil(t, request.Ext, "Wrong request.ext. Expected nil, Got %v", request.Ext) + assert.Nil(t, rpRequest.Ext, "Wrong request.ext. Expected nil, Got %v", rpRequest.Ext) if rpRequest.Imp[0].ID == "test-imp-banner-id" { var rpExt rubiconBannerExt @@ -1425,6 +1425,99 @@ func TestOpenRTBStandardResponse(t *testing.T) { assert.Equal(t, "1234567890", theBid.ID, "Bad bid ID. Expected %s, got %s", "1234567890", theBid.ID) } +func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { + request := &openrtb.BidRequest{ + ID: "test-request-id", + Imp: []openrtb.Imp{{ + ID: "test-imp-id", + Banner: &openrtb.Banner{ + Format: []openrtb.Format{{ + W: 320, + H: 50, + }}, + }, + Ext: json.RawMessage(`{"bidder": { + "accountId": 2763, + "siteId": 68780, + "zoneId": 327642 + }}`), + }}, + Ext: json.RawMessage(`{"rubicon": { + "debug": { + "cpmOverride" : 10 + }}}`), + } + + requestJson, _ := json.Marshal(request) + reqData := &adapters.RequestData{ + Method: "POST", + Uri: "test-uri", + Body: requestJson, + Headers: nil, + } + + httpResp := &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id":"test-request-id","seatbid":[{"bid":[{"id":"1234567890","impid":"test-imp-id","price": 2,"crid":"4122982","adm":"some ad","h": 50,"w": 320,"ext":{"bidder":{"rp":{"targeting": {"key": "rpfl_2763", "values":["43_tier0100"]},"mime": "text/html","size_id": 43}}}}]}]}`), + } + + bidder := new(RubiconAdapter) + bidResponse, errs := bidder.MakeBids(request, reqData, httpResp) + + assert.Empty(t, errs, "Expected 0 errors. Got %d", len(errs)) + + assert.Equal(t, float64(10), bidResponse.Bids[0].Bid.Price, + "Expected Price 10. Got: %s", bidResponse.Bids[0].Bid.Price) +} + +func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { + request := &openrtb.BidRequest{ + ID: "test-request-id", + Imp: []openrtb.Imp{{ + ID: "test-imp-id", + Banner: &openrtb.Banner{ + Format: []openrtb.Format{{ + W: 320, + H: 50, + }}, + }, + Ext: json.RawMessage(`{"bidder": { + "accountId": 2763, + "siteId": 68780, + "zoneId": 327642, + "debug": { + "cpmOverride" : 20 + } + }}`), + }}, + Ext: json.RawMessage(`{"rubicon": { + "debug": { + "cpmOverride" : 10 + }}}`), + } + + requestJson, _ := json.Marshal(request) + reqData := &adapters.RequestData{ + Method: "POST", + Uri: "test-uri", + Body: requestJson, + Headers: nil, + } + + httpResp := &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id":"test-request-id","seatbid":[{"bid":[{"id":"1234567890","impid":"test-imp-id","price": 2,"crid":"4122982","adm":"some ad","h": 50,"w": 320,"ext":{"bidder":{"rp":{"targeting": {"key": "rpfl_2763", "values":["43_tier0100"]},"mime": "text/html","size_id": 43}}}}]}]}`), + } + + bidder := new(RubiconAdapter) + bidResponse, errs := bidder.MakeBids(request, reqData, httpResp) + + assert.Empty(t, errs, "Expected 0 errors. Got %d", len(errs)) + + assert.Equal(t, float64(20), bidResponse.Bids[0].Bid.Price, + "Expected Price 20. Got: %s", bidResponse.Bids[0].Bid.Price) +} + func TestOpenRTBCopyBidIdFromResponseIfZero(t *testing.T) { request := &openrtb.BidRequest{ ID: "test-request-id", diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index d588af82184..17585a8ee93 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -12,6 +12,7 @@ type ExtImpRubicon struct { Inventory json.RawMessage `json:"inventory,omitempty"` Visitor json.RawMessage `json:"visitor,omitempty"` Video rubiconVideoParams `json:"video"` + Debug impExtRubiconDebug `json:"debug,omitempty"` } // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.video @@ -23,3 +24,8 @@ type rubiconVideoParams struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` } + +// rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.debug +type impExtRubiconDebug struct { + CpmOverride float64 `json:"cpmOverride,omitempty"` +} From 6e5d0445ad29a9af1259a175843cb3f531cacd5a Mon Sep 17 00:00:00 2001 From: ddantuonobeintoo <58686785+ddantuonobeintoo@users.noreply.github.com> Date: Thu, 14 May 2020 16:59:44 +0200 Subject: [PATCH 081/603] Add Beintoo adapter (#1274) * Add Beintoo adapter --- adapters/beintoo/beintoo.go | 222 ++++++++++++++++++ adapters/beintoo/beintoo_test.go | 12 + .../beintootest/exemplary/minimal-banner.json | 117 +++++++++ .../beintootest/params/race/banner.json | 4 + .../supplemental/add-bidfloor.json | 42 ++++ .../bad-imp-banner-missing-sizes.json | 32 +++ .../supplemental/bad-imp-ext-tagid-value.json | 33 +++ .../supplemental/build-banner-object.json | 61 +++++ .../invalid-request-no-banner.json | 26 ++ .../invalid-response-no-bids.json | 45 ++++ .../invalid-response-unmarshall-error.json | 63 +++++ .../supplemental/no-imps-in-request.json | 18 ++ .../supplemental/server-error-code.json | 52 ++++ .../supplemental/server-no-content.json | 44 ++++ .../site-domain-and-url-correctly-parsed.json | 61 +++++ adapters/beintoo/params_test.go | 53 +++++ adapters/beintoo/usersync.go | 12 + adapters/beintoo/usersync_test.go | 35 +++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_beintoo.go | 6 + static/bidder-info/beintoo.yaml | 6 + static/bidder-params/beintoo.json | 18 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 971 insertions(+) create mode 100644 adapters/beintoo/beintoo.go create mode 100644 adapters/beintoo/beintoo_test.go create mode 100644 adapters/beintoo/beintootest/exemplary/minimal-banner.json create mode 100644 adapters/beintoo/beintootest/params/race/banner.json create mode 100644 adapters/beintoo/beintootest/supplemental/add-bidfloor.json create mode 100644 adapters/beintoo/beintootest/supplemental/bad-imp-banner-missing-sizes.json create mode 100644 adapters/beintoo/beintootest/supplemental/bad-imp-ext-tagid-value.json create mode 100644 adapters/beintoo/beintootest/supplemental/build-banner-object.json create mode 100644 adapters/beintoo/beintootest/supplemental/invalid-request-no-banner.json create mode 100644 adapters/beintoo/beintootest/supplemental/invalid-response-no-bids.json create mode 100644 adapters/beintoo/beintootest/supplemental/invalid-response-unmarshall-error.json create mode 100644 adapters/beintoo/beintootest/supplemental/no-imps-in-request.json create mode 100644 adapters/beintoo/beintootest/supplemental/server-error-code.json create mode 100644 adapters/beintoo/beintootest/supplemental/server-no-content.json create mode 100644 adapters/beintoo/beintootest/supplemental/site-domain-and-url-correctly-parsed.json create mode 100644 adapters/beintoo/params_test.go create mode 100644 adapters/beintoo/usersync.go create mode 100644 adapters/beintoo/usersync_test.go create mode 100644 openrtb_ext/imp_beintoo.go create mode 100644 static/bidder-info/beintoo.yaml create mode 100644 static/bidder-params/beintoo.json diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go new file mode 100644 index 00000000000..fb511f12075 --- /dev/null +++ b/adapters/beintoo/beintoo.go @@ -0,0 +1,222 @@ +package beintoo + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type BeintooAdapter struct { + endpoint string +} + +func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("No Imps in Bid Request"), + }} + } + + if errors := preprocess(request); errors != nil && len(errors) > 0 { + return nil, append(errors, &errortypes.BadInput{ + Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errors), + }) + } + + data, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error in packaging request to JSON"), + }} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Device != nil { + addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA) + addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP) + addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language) + if request.Device.DNT != nil { + addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT))) + } + } + if request.Site != nil { + addHeaderIfNonEmpty(headers, "Referer", request.Site.Page) + } + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: data, + Headers: headers, + }}, errors +} + +func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBeintoo, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + var beintooExt openrtb_ext.ExtImpBeintoo + if err := json.Unmarshal(bidderExt.Bidder, &beintooExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID), + } + } + + tagIDValidation, err := strconv.ParseInt(beintooExt.TagID, 10, 64) + if err != nil || tagIDValidation == 0 { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID), + } + } + + return &beintooExt, nil +} + +func buildImpBanner(imp *openrtb.Imp) error { + imp.Ext = nil + + if imp.Banner == nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Request needs to include a Banner object"), + } + } + + bannerCopy := *imp.Banner + banner := &bannerCopy + + if banner.W == nil && banner.H == nil { + if len(banner.Format) == 0 { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Need at least one size to build request"), + } + } + format := banner.Format[0] + banner.Format = banner.Format[1:] + banner.W = &format.W + banner.H = &format.H + imp.Banner = banner + } + + return nil +} + +// Add Beintoo required properties to Imp object +func addImpProps(imp *openrtb.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) { + imp.TagID = BeintooExt.TagID + imp.Secure = secure + + if BeintooExt.BidFloor != "" { + bidFloor, err := strconv.ParseFloat(BeintooExt.BidFloor, 64) + if err != nil { + bidFloor = 0 + } + + if bidFloor > 0 { + imp.BidFloor = bidFloor + } + } + + return +} + +// Adding header fields to request header +func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { + if len(headerValue) > 0 { + headers.Add(headerName, headerValue) + } +} + +// Handle request errors and formatting to be sent to Beintoo +func preprocess(request *openrtb.BidRequest) []error { + errors := make([]error, 0, len(request.Imp)) + resImps := make([]openrtb.Imp, 0, len(request.Imp)) + secure := int8(0) + + if request.Site != nil && request.Site.Page != "" { + pageURL, err := url.Parse(request.Site.Page) + if err == nil && pageURL.Scheme == "https" { + secure = int8(1) + } + } + + for _, imp := range request.Imp { + beintooExt, err := unpackImpExt(&imp) + if err != nil { + errors = append(errors, err) + return errors + } + + addImpProps(&imp, &secure, beintooExt) + + if err := buildImpBanner(&imp); err != nil { + errors = append(errors, err) + return errors + } + resImps = append(resImps, imp) + } + + request.Imp = resImps + + return errors +} + +// MakeBids make the bids for the bid response. +func (a *BeintooAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + // no bid response + return nil, nil + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + sb.Bid[i].ImpID = sb.Bid[i].ID + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: "banner", + }) + } + } + + return bidResponse, nil + +} + +func NewBeintooBidder(endpoint string) *BeintooAdapter { + return &BeintooAdapter{ + endpoint: endpoint, + } +} diff --git a/adapters/beintoo/beintoo_test.go b/adapters/beintoo/beintoo_test.go new file mode 100644 index 00000000000..863da1513e5 --- /dev/null +++ b/adapters/beintoo/beintoo_test.go @@ -0,0 +1,12 @@ +package beintoo + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + beintooAdapter := NewBeintooBidder("https://ib.beintoo.com") + adapterstest.RunJSONBidderTest(t, "beintootest", beintooAdapter) +} diff --git a/adapters/beintoo/beintootest/exemplary/minimal-banner.json b/adapters/beintoo/beintootest/exemplary/minimal-banner.json new file mode 100644 index 00000000000..60e481c507c --- /dev/null +++ b/adapters/beintoo/beintootest/exemplary/minimal-banner.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ib.beintoo.com", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Referer": [ + "http://www.publisher.com/awesome/site?with=some¶meters=here" + ], + "Dnt": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "tagid": "25251", + "secure": 0 + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + }, + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
" +const adSourceURL = "https://ad.yieldlab.net/d/%v/%v/%v?%v" +const creativeID = "%v%v%v" diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go new file mode 100644 index 00000000000..8c230c15b15 --- /dev/null +++ b/adapters/yieldlab/params_test.go @@ -0,0 +1,63 @@ +package yieldlab + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/yieldlab.json +// +// These also validate the format of the external API: request.imp[i].ext.yieldlab + +// TestValidParams makes sure that the yieldlab schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected yieldlab params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the yieldlab schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"adslotId": "123","supplyId":"23456","adSize":"100x100"}`, + `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf"}`, + `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf","targeting":{"a":"b"}}`, + `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`, + `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`, +} + +var invalidParams = []string{ + `{"supplyId":"23456","adSize":"100x100"}`, + `{"adslotId": "123","adSize":"100x100","extId":"asdf"}`, + `{"adslotId": "123","supplyId":"23456","extId":"asdf","targeting":{"a":"b"}}`, + `{"adslotId": "123","supplyId":"23456"}`, + `{"adSize":"100x100","supplyId":"23456"}`, + `{"adslotId": "123","adSize":"100x100"}`, + `{"supplyId":"23456"}`, + `{"adslotId": "123"}`, + `{}`, + `[]`, + `{"a":"b"}`, + `null`, +} diff --git a/adapters/yieldlab/types.go b/adapters/yieldlab/types.go new file mode 100644 index 00000000000..90612700713 --- /dev/null +++ b/adapters/yieldlab/types.go @@ -0,0 +1,29 @@ +package yieldlab + +import ( + "strconv" + "time" +) + +type bidResponse struct { + ID uint64 `json:"id"` + Price uint `json:"price"` + Advertiser string `json:"advertiser"` + Adsize string `json:"adsize"` + Pid uint64 `json:"pid"` + Did uint64 `json:"did"` + Pvid string `json:"pvid"` +} + +type cacheBuster func() string + +type weekGenerator func() string + +var defaultCacheBuster cacheBuster = func() string { + return strconv.FormatInt(time.Now().Unix(), 10) +} + +var defaultWeekGenerator weekGenerator = func() string { + _, week := time.Now().ISOWeek() + return strconv.Itoa(week) +} diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go new file mode 100644 index 00000000000..3ee9a3fdfb5 --- /dev/null +++ b/adapters/yieldlab/usersync.go @@ -0,0 +1,12 @@ +package yieldlab + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("yieldlab", 70, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go new file mode 100644 index 00000000000..3892c16bf05 --- /dev/null +++ b/adapters/yieldlab/usersync_test.go @@ -0,0 +1,26 @@ +package yieldlab + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" +) + +func TestYieldlabSyncer(t *testing.T) { + temp := template.Must(template.New("sync-template").Parse("https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25")) + syncer := NewYieldlabSyncer(temp) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + assert.NoError(t, err) + assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 70, syncer.GDPRVendorID()) + assert.False(t, syncInfo.SupportCORS) +} diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go new file mode 100644 index 00000000000..20f3674797d --- /dev/null +++ b/adapters/yieldlab/yieldlab.go @@ -0,0 +1,314 @@ +package yieldlab + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strconv" + "strings" + + "github.com/mxmCherry/openrtb" + "golang.org/x/text/currency" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// YieldlabAdapter connects the Yieldlab API to prebid server +type YieldlabAdapter struct { + endpoint string + cacheBuster cacheBuster + getWeek weekGenerator +} + +// NewYieldlabBidder returns a new YieldlabBidder instance +func NewYieldlabBidder(endpoint string) *YieldlabAdapter { + return &YieldlabAdapter{ + endpoint: endpoint, + cacheBuster: defaultCacheBuster, + getWeek: defaultWeekGenerator, + } +} + +// Builds endpoint url based on adapter-specific pub settings from imp.ext +func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { + uri, err := url.Parse(a.endpoint) + if err != nil { + return "", fmt.Errorf("failed to parse yieldlab endpoint: %v", err) + } + + uri.Path = path.Join(uri.Path, params.AdslotID) + q := uri.Query() + q.Set("content", "json") + q.Set("pvid", "true") + q.Set("ts", a.cacheBuster()) + q.Set("t", a.makeTargetingValues(params)) + + if req.User != nil && req.User.BuyerUID != "" { + q.Set("ids", "ylid:"+req.User.BuyerUID) + } + + if req.Device != nil { + q.Set("yl_rtb_ifa", req.Device.IFA) + q.Set("yl_rtb_devicetype", fmt.Sprintf("%v", req.Device.DeviceType)) + + if req.Device.ConnectionType != nil { + q.Set("yl_rtb_connectiontype", fmt.Sprintf("%v", req.Device.ConnectionType.Val())) + } + + if req.Device.Geo != nil { + q.Set("lat", fmt.Sprintf("%v", req.Device.Geo.Lat)) + q.Set("lon", fmt.Sprintf("%v", req.Device.Geo.Lon)) + } + } + + if req.App != nil { + q.Set("pubappname", req.App.Name) + q.Set("pubbundlename", req.App.Bundle) + } + + gdpr, consent, err := a.getGDPR(req) + if err != nil { + return "", err + } + if gdpr != "" && consent != "" { + q.Set("gdpr", gdpr) + q.Set("consent", consent) + } + + uri.RawQuery = q.Encode() + + return uri.String(), nil +} + +func (a *YieldlabAdapter) getGDPR(request *openrtb.BidRequest) (string, string, error) { + gdpr := "" + var extRegs openrtb_ext.ExtRegs + if request.Regs != nil { + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { + return "", "", fmt.Errorf("failed to parse ExtRegs in Yieldlab GDPR check: %v", err) + } + if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { + gdpr = strconv.Itoa(int(*extRegs.GDPR)) + } + } + + consent := "" + if request.User != nil && request.User.Ext != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + return "", "", fmt.Errorf("failed to parse ExtUser in Yieldlab GDPR check: %v", err) + } + consent = extUser.Consent + } + + return gdpr, consent, nil +} + +func (a *YieldlabAdapter) makeTargetingValues(params *openrtb_ext.ExtImpYieldlab) string { + values := url.Values{} + for k, v := range params.Targeting { + values.Set(k, v) + } + return values.Encode() +} + +func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{fmt.Errorf("invalid request %+v, no Impressions given", request)} + } + + bidURL, err := a.makeEndpointURL(request, a.mergeParams(a.parseRequest(request))) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Accept", "application/json") + if request.Site != nil { + headers.Add("Referer", request.Site.Page) + } + if request.Device != nil { + headers.Add("User-Agent", request.Device.UA) + headers.Add("X-Forwarded-For", request.Device.IP) + } + if request.User != nil { + headers.Add("Cookie", "id="+request.User.BuyerUID) + } + + return []*adapters.RequestData{{ + Method: "GET", + Uri: bidURL, + Headers: headers, + }}, nil +} + +// parseRequest extracts the Yieldlab request information from the request +func (a *YieldlabAdapter) parseRequest(request *openrtb.BidRequest) []*openrtb_ext.ExtImpYieldlab { + params := make([]*openrtb_ext.ExtImpYieldlab, 0) + + for i := 0; i < len(request.Imp); i++ { + bidderExt := new(adapters.ExtImpBidder) + if err := json.Unmarshal(request.Imp[i].Ext, bidderExt); err != nil { + continue + } + + yieldlabExt := new(openrtb_ext.ExtImpYieldlab) + if err := json.Unmarshal(bidderExt.Bidder, yieldlabExt); err != nil { + continue + } + + params = append(params, yieldlabExt) + } + + return params +} + +func (a *YieldlabAdapter) mergeParams(params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab { + var adSlotIds []string + targeting := make(map[string]string) + + for _, p := range params { + adSlotIds = append(adSlotIds, p.AdslotID) + for k, v := range p.Targeting { + targeting[k] = v + } + } + + return &openrtb_ext.ExtImpYieldlab{ + AdslotID: strings.Join(adSlotIds, adSlotIdSeparator), + Targeting: targeting, + } +} + +// MakeBids make the bids for the bid response. +func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode != 200 { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("failed to resolve bids from yieldlab response: Unexpected response code %v", response.StatusCode), + }, + } + } + + bids := make([]*bidResponse, 0) + if err := json.Unmarshal(response.Body, &bids); err != nil { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("failed to parse bids response from yieldlab: %v", err), + }, + } + } + + params := a.parseRequest(internalRequest) + + bidderResponse := &adapters.BidderResponse{ + Currency: currency.EUR.String(), + Bids: []*adapters.TypedBid{}, + } + + for i, bid := range bids { + width, height, err := splitSize(bid.Adsize) + if err != nil { + return nil, []error{err} + } + + req := a.findBidReq(bid.ID, params) + if req == nil { + return nil, []error{ + fmt.Errorf("failed to find yieldlab request for adslotID %v. This is most likely a programming issue", bid.ID), + } + } + + var bidType openrtb_ext.BidType + responseBid := &openrtb.Bid{ + ID: strconv.FormatUint(bid.ID, 10), + Price: float64(bid.Price) / 100, + ImpID: internalRequest.Imp[i].ID, + CrID: a.makeCreativeID(req, bid), + DealID: strconv.FormatUint(bid.Pid, 10), + W: width, + H: height, + } + + if internalRequest.Imp[i].Video != nil { + bidType = openrtb_ext.BidTypeVideo + responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid) + + } else if internalRequest.Imp[i].Banner != nil { + bidType = openrtb_ext.BidTypeBanner + responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid) + } else { + // Yieldlab adapter currently doesn't support Audio and Native ads + continue + } + + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + BidType: bidType, + Bid: responseBid, + }) + } + + return bidderResponse, nil +} + +func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab { + slotIdStr := strconv.FormatUint(adslotID, 10) + for _, p := range params { + if p.AdslotID == slotIdStr { + return p + } + } + + return nil +} + +func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { + return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res)) +} + +func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { + val := url.Values{} + val.Set("ts", a.cacheBuster()) + val.Set("id", ext.ExtId) + val.Set("pvid", res.Pvid) + + if req.User != nil { + val.Set("ids", "ylid:"+req.User.BuyerUID) + } + + gdpr, consent, err := a.getGDPR(req) + if err == nil && gdpr != "" && consent != "" { + val.Set("gdpr", gdpr) + val.Set("consent", consent) + } + + return fmt.Sprintf(adSourceURL, ext.AdslotID, ext.SupplyID, res.Adsize, val.Encode()) +} + +func (a *YieldlabAdapter) makeCreativeID(req *openrtb_ext.ExtImpYieldlab, bid *bidResponse) string { + return fmt.Sprintf(creativeID, req.AdslotID, bid.Pid, a.getWeek()) +} + +func splitSize(size string) (uint64, uint64, error) { + sizeParts := strings.Split(size, adsizeSeparator) + if len(sizeParts) != 2 { + return 0, 0, nil + } + + width, err := strconv.ParseUint(sizeParts[0], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err) + } + + height, err := strconv.ParseUint(sizeParts[1], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err) + } + + return width, height, nil + +} diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go new file mode 100644 index 00000000000..b6ca0507ab8 --- /dev/null +++ b/adapters/yieldlab/yieldlab_test.go @@ -0,0 +1,128 @@ +package yieldlab + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +const testURL = "https://ad.yieldlab.net/testing/" + +var testCacheBuster cacheBuster = func() string { + return "testing" +} + +var testWeekGenerator weekGenerator = func() string { + return "33" +} + +func newTestYieldlabBidder(endpoint string) *YieldlabAdapter { + return &YieldlabAdapter{ + endpoint: endpoint, + cacheBuster: testCacheBuster, + getWeek: testWeekGenerator, + } +} + +func TestNewYieldlabBidder(t *testing.T) { + bid := NewYieldlabBidder(testURL) + assert.NotNil(t, bid) + assert.Equal(t, bid.endpoint, testURL) + assert.NotNil(t, bid.cacheBuster) + assert.NotNil(t, bid.getWeek) +} + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "yieldlabtest", newTestYieldlabBidder(testURL)) +} + +func Test_splitSize(t *testing.T) { + type args struct { + size string + } + tests := []struct { + name string + args args + want uint64 + want1 uint64 + wantErr bool + }{ + { + name: "valid", + args: args{ + size: "300x800", + }, + want: 300, + want1: 800, + wantErr: false, + }, + { + name: "empty", + args: args{ + size: "", + }, + want: 0, + want1: 0, + wantErr: false, + }, + { + name: "invalid", + args: args{ + size: "test", + }, + want: 0, + want1: 0, + wantErr: false, + }, + { + name: "invalid_height", + args: args{ + size: "200xtest", + }, + want: 0, + want1: 0, + wantErr: true, + }, + { + name: "invalid_width", + args: args{ + size: "testx200", + }, + want: 0, + want1: 0, + wantErr: true, + }, + { + name: "invalid_separator", + args: args{ + size: "200y200", + }, + want: 0, + want1: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := splitSize(tt.args.size) + if (err != nil) != tt.wantErr { + t.Errorf("splitSize() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("splitSize() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("splitSize() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) { + bid := NewYieldlabBidder("test$:/something§") + _, err := bid.makeEndpointURL(nil, nil) + assert.Error(t, err) +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/banner.json b/adapters/yieldlab/yieldlabtest/exemplary/banner.json new file mode 100644 index 00000000000..8dd94404097 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/banner.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "adSize": "728x90", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json new file mode 100644 index 00000000000..381ba688e09 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "adSize": "728x90", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c", + "ext": { + "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json new file mode 100644 index 00000000000..9e970ae79b5 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "adSize": "728x90", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + }, + "video": { + "context": "instream", + "mimes": [ + "video/mp4" + ], + "playerSize": [ + [ + 400, + 600 + ] + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2 + ], + "w": 1, + "h": 2, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ] + } + } + ], + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json new file mode 100644 index 00000000000..67d526b3400 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "adSize": "728x90", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + }, + "video": { + "context": "instream", + "mimes": [ + "video/mp4" + ], + "playerSize": [ + [ + 400, + 600 + ] + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2 + ], + "w": 1, + "h": 2, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ] + } + } + ], + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pubappname=Awesome+App&pubbundlename=com.app.awesome&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "video" + } + ] + } + ] +} diff --git a/config/config.go b/config/config.go index 5c66f4cdf02..86f2e9a98a0 100755 --- a/config/config.go +++ b/config/config.go @@ -551,6 +551,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_sc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") @@ -760,6 +761,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid") + v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index b91f01a7e9a..b69b5b50e13 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -73,6 +73,7 @@ import ( "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yeahmobi" + "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" "github.com/prebid/prebid-server/adapters/zeroclickfraud" @@ -153,6 +154,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderUcfunnel: ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint), openrtb_ext.BidderUnruly: unruly.NewUnrulyBidder(client, cfg.Adapters[string(openrtb_ext.BidderUnruly)].Endpoint), openrtb_ext.BidderValueImpression: valueimpression.NewValueImpressionBidder(cfg.Adapters[string(openrtb_ext.BidderValueImpression)].Endpoint), + openrtb_ext.BidderYieldlab: yieldlab.NewYieldlabBidder(cfg.Adapters[string(openrtb_ext.BidderYieldlab)].Endpoint), openrtb_ext.BidderVerizonMedia: verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint), openrtb_ext.BidderVisx: visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint), openrtb_ext.BidderVrtcal: vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint), diff --git a/go.mod b/go.mod index 89cc69e4519..8de6f10e4b9 100644 --- a/go.mod +++ b/go.mod @@ -7,23 +7,17 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/aerospike/aerospike-client-go v2.7.2+incompatible github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible - github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 - github.com/didip/tollbooth v4.0.2+incompatible github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd - github.com/go-redis/redis v6.15.7+incompatible - github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5 github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/golang/snappy v0.0.1 github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb v1.6.1 // indirect github.com/julienschmidt/httprouter v1.1.0 @@ -37,10 +31,8 @@ require ( github.com/mxmCherry/openrtb v11.0.0+incompatible github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/prebid/go-gdpr v0.7.0 - github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect @@ -48,27 +40,24 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.5.0 github.com/sergi/go-diff v1.0.0 // indirect - github.com/sirupsen/logrus v1.4.2 github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.1.1 // indirect github.com/spf13/cast v1.2.0 // indirect github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect github.com/spf13/pflag v1.0.2 // indirect github.com/spf13/viper v1.1.0 + github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.5.1 - github.com/valyala/fasthttp v1.9.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 - github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect golang.org/x/text v0.3.0 - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index f929408f0f3..176bacfc20a 100644 --- a/go.sum +++ b/go.sum @@ -6,57 +6,35 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/aerospike/aerospike-client-go v2.7.2+incompatible h1:bWbRf8trg1FhKF7u43KLGNfOH60RlvIgQjpaS107DZ8= -github.com/aerospike/aerospike-client-go v2.7.2+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coocood/freecache v1.0.1 h1:oFyo4msX2c0QIKU+kuMJUwsKamJ+AKc2JJrKcMszJ5M= github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= -github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= -github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5 h1:ZZVxQRCm4ewuoqqLBJ7LHpsk4OGx2PkyCsRKLq4oHgE= -github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -67,17 +45,6 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -99,16 +66,12 @@ github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prebid/go-gdpr v0.7.0 h1:m4E/FjUhTBMciDsd3lQlbzFyXLzNK+JQkFmInJpFAwc= github.com/prebid/go-gdpr v0.7.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= -github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= -github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= @@ -123,8 +86,6 @@ github.com/rs/cors v1.5.0 h1:dgSHE6+ia18arGOTIYQKKGWLvEbGvmbNE6NfxhoNHUY= github.com/rs/cors v1.5.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= @@ -140,16 +101,10 @@ github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw= -github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 h1:Va10CytCCYRm4xBTses5ZDeDjeIQjhaiC9nRCe/yflI= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303/go.mod h1:Xdcad1nGVhQfhoV0go+/4WaI/RZkWlvfjkVCdpMTxPY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -158,16 +113,12 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= -github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -178,7 +129,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -186,14 +136,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/p golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f2f8e7c67ab..8a53e4adcf2 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -91,6 +91,7 @@ const ( BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" BidderYeahmobi BidderName = "yeahmobi" + BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" BidderZeroClickFraud BidderName = "zeroclickfraud" @@ -166,6 +167,7 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "vrtcal": BidderVrtcal, "yeahmobi": BidderYeahmobi, + "yieldlab": BidderYieldlab, "yieldmo": BidderYieldmo, "yieldone": BidderYieldone, "zeroclickfraud": BidderZeroClickFraud, diff --git a/openrtb_ext/imp_yieldlab.go b/openrtb_ext/imp_yieldlab.go new file mode 100644 index 00000000000..604b7e8ceab --- /dev/null +++ b/openrtb_ext/imp_yieldlab.go @@ -0,0 +1,10 @@ +package openrtb_ext + +// ExtImpYieldlab defines the contract for bidrequest.imp[i].ext.yieldlab +type ExtImpYieldlab struct { + AdslotID string `json:"adslotId"` + SupplyID string `json:"supplyId"` + AdSize string `json:"adSize"` + Targeting map[string]string `json:"targeting"` + ExtId string `json:"extId"` +} diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml new file mode 100644 index 00000000000..654e6c749cb --- /dev/null +++ b/static/bidder-info/yieldlab.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "solutions@yieldlab.de" +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/yieldlab.json b/static/bidder-params/yieldlab.json new file mode 100644 index 00000000000..900d65da6e5 --- /dev/null +++ b/static/bidder-params/yieldlab.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Yieldlab Adapter Params", + "description": "A schema which validates params accepted by the Yieldlab adapter", + "type": "object", + "properties": { + "adslotId": { + "type": "string", + "description": "Yieldlab ID of the ad slot" + }, + "supplyId": { + "type": "string", + "description": "Yieldlab ID of the supply" + }, + "adSize": { + "type": "string", + "description": "Size of the adslot in pixel, e.g. 200x50" + }, + "extId": { + "type": "string", + "description": "External ID used for reporting" + }, + "targeting": { + "type": "object", + "description": "Targeting information in key value pairs" + } + }, + "required": [ + "adslotId", + "supplyId", + "adSize" + ] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 3f12ee7f728..791a00de0a9 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -61,6 +61,7 @@ import ( "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" + "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" "github.com/prebid/prebid-server/adapters/zeroclickfraud" @@ -131,6 +132,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 20fce80c83a..9aae284da2a 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -67,6 +67,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, + string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, From d29a749f6835ef521a6ddfaa2818ef4fa66aabf0 Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 2 Jun 2020 21:59:01 +0300 Subject: [PATCH 101/603] Update adtelligent ortb endpoint (#1318) --- adapters/adtelligent/adtelligent.go | 1 - adapters/adtelligent/adtelligent_test.go | 2 +- .../adtelligenttest/exemplary/media-type-mapping.json | 2 +- .../adtelligent/adtelligenttest/exemplary/simple-banner.json | 2 +- .../adtelligent/adtelligenttest/exemplary/simple-video.json | 2 +- .../adtelligenttest/supplemental/explicit-dimensions.json | 2 +- .../supplemental/wrong-impression-mapping.json | 2 +- config/config.go | 4 ++-- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index 7f0262fdc92..78a71dcf9cd 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -55,7 +55,6 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * imps := request.Imp request.Imp = make([]openrtb.Imp, 0, len(imps)) - for sourceId, impIds := range imp2source { request.Imp = request.Imp[:0] diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 63655da677e..b8894c5e4d9 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -7,5 +7,5 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://hb.adtelligent.com/auction")) + adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://ghb.adtelligent.com/pbs/ortb")) } diff --git a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json index 67ad2fd2915..553ec61833b 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://hb.adtelligent.com/auction?aid=1000", + "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json index 6648229de95..a06477b4d18 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://hb.adtelligent.com/auction?aid=1000", + "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json index 97769651997..f108cc94b17 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://hb.adtelligent.com/auction?aid=1000", + "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json index 9dc279bcd1c..6155e9bc56b 100644 --- a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json +++ b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://hb.adtelligent.com/auction?aid=1000", + "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json index 94df34af40d..2e5aeff311f 100644 --- a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json +++ b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://hb.adtelligent.com/auction?aid=1000", + "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000", "body": { "id": "test-request-id", "imp": [ diff --git a/config/config.go b/config/config.go index 86f2e9a98a0..3b34d3a4815 100755 --- a/config/config.go +++ b/config/config.go @@ -500,7 +500,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -702,7 +702,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") - v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") + v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") From b5993cd0e5912638b03cea039466673325dee40b Mon Sep 17 00:00:00 2001 From: Seba Perez Date: Tue, 2 Jun 2020 16:05:03 -0300 Subject: [PATCH 102/603] Change on eplanning endpoint (#1327) --- adapters/eplanning/eplanning.go | 1 - adapters/eplanning/eplanning_test.go | 2 +- adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json | 2 +- adapters/eplanning/eplanningtest/exemplary/simple-banner.json | 2 +- adapters/eplanning/eplanningtest/exemplary/two-banners.json | 2 +- .../supplemental/app-domain-and-url-correctly-parsed.json | 2 +- .../eplanningtest/supplemental/banner-no-size-sends-1x1.json | 2 +- .../eplanningtest/supplemental/invalid-response-no-bids.json | 2 +- .../supplemental/invalid-response-unmarshall-error.json | 2 +- .../eplanningtest/supplemental/server-bad-request.json | 2 +- .../eplanning/eplanningtest/supplemental/server-error-code.json | 2 +- .../eplanning/eplanningtest/supplemental/server-no-content.json | 2 +- .../supplemental/site-domain-and-url-correctly-parsed.json | 2 +- config/config.go | 2 +- 14 files changed, 13 insertions(+), 14 deletions(-) diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 5fb9ccf27c2..2a46b5469e0 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -133,7 +133,6 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn uriObj.Path = uriObj.Path + fmt.Sprintf("/%s/%s/%s/%s", clientID, dfpClientID, requestTarget, sec) query := url.Values{} - query.Set("r", "pbs") query.Set("ncb", "1") if request.App == nil { query.Set("ur", pageURL) diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index d2c331d456d..461fb849ced 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -8,7 +8,7 @@ import ( ) func TestJsonSamples(t *testing.T) { - eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/hb/1") + eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/pbs/1") eplanningAdapter.testing = true adapterstest.RunJSONBidderTest(t, "eplanningtest", eplanningAdapter) } diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json index e602877f27f..a67a86d18e6 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json index 0403e59a763..9146bb4afb5 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&r=pbs&uid=2154987&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&uid=2154987&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/two-banners.json b/adapters/eplanning/eplanningtest/exemplary/two-banners.json index 8c4ca0214db..174c8ce3fc6 100644 --- a/adapters/eplanning/eplanningtest/exemplary/two-banners.json +++ b/adapters/eplanning/eplanningtest/exemplary/two-banners.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json index e6e25566b6a..04df82f6668 100644 --- a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json +++ b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1&r=pbs", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json index f1bc29e1afc..f02eb80fe41 100644 --- a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json +++ b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json index 978989e295f..8bdcfddd733 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json index 7198f4ee117..9f5b2d7fc03 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json index 938ede62664..2ef03648884 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json index eaa2a677f93..76e75a5c203 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json index d1feb865f0d..02f1fa46d33 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json index 2f43b9aac2f..581cb1d5b46 100644 --- a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json +++ b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere", + "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere", "body": {} }, "mockResponse": { diff --git a/config/config.go b/config/config.go index 3b34d3a4815..79a31db154a 100755 --- a/config/config.go +++ b/config/config.go @@ -718,7 +718,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1") + v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/pbs/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") From cd9116e80c514a846600a4c14f57e608ffaedf32 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:33:07 -0400 Subject: [PATCH 103/603] Enable full TCF2 support (#1302) * New config options * Enble TCF2 fields and logic * Resolves some PR comments * More tests * gofmt * Added enforcement tests for split GDPR/GDPRGeo * Testing tweaks * No longer ignore enforce purpose 1 on allowSync() * Removes Purpose 4 --- config/config.go | 29 +++ endpoints/auction_test.go | 5 +- endpoints/cookie_sync_test.go | 4 +- endpoints/setuid_test.go | 4 +- exchange/utils.go | 4 +- exchange/utils_test.go | 6 +- gdpr/gdpr.go | 2 +- gdpr/impl.go | 96 ++++++-- gdpr/impl_test.go | 390 ++++++++++++++++++++++++++++++- gdpr/vendorlist-fetching_test.go | 86 ++++++- go.mod | 3 +- go.sum | 6 +- privacy/enforcement.go | 19 +- privacy/enforcement_test.go | 108 ++++++--- 14 files changed, 677 insertions(+), 85 deletions(-) diff --git a/config/config.go b/config/config.go index 79a31db154a..5f19629d2db 100755 --- a/config/config.go +++ b/config/config.go @@ -142,6 +142,7 @@ type GDPR struct { Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]int + TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` } @@ -165,6 +166,26 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } +// TCF2 defines the TCF2 specific configurations for GDPR +type TCF2 struct { + Enabled bool `mapstructure:"enabled"` + Purpose1 PurposeDetail `mapstructure:"purpose1"` + Purpose2 PurposeDetail `mapstructure:"purpose2"` + Purpose7 PurposeDetail `mapstructure:"purpose7"` + SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` + PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"` +} + +// Making a purpose struct so purpose specific details can be added later. +type PurposeDetail struct { + Enabled bool `mapstructure:"enabled"` +} + +type PurposeOneTreatement struct { + Enabled bool `mapstructure:"enabled"` + AccessAllowed bool `mapstructure:"access_allowed"` +} + type CCPA struct { Enforce bool `mapstructure:"enforce"` } @@ -774,6 +795,14 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) + v.SetDefault("gdpr.tcf2.enabled", true) + v.SetDefault("gdpr.tcf2.purpose1.enabled", true) + v.SetDefault("gdpr.tcf2.purpose2.enabled", true) + v.SetDefault("gdpr.tcf2.purpose4.enabled", true) + v.SetDefault("gdpr.tcf2.purpose7.enabled", true) + v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) + v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) + v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("ccpa.enforce", false) v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 3035a6d45fb..5e9e9639a9c 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -407,6 +407,7 @@ type auctionMockPermissions struct { allowBidderSync bool allowHostCookies bool allowPI bool + allowGeo bool } func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { @@ -417,8 +418,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { - return m.allowPI, nil +func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { + return m.allowPI, m.allowGeo, nil } func (m *auctionMockPermissions) AMPException() bool { diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index bb766aa92e7..824e32f1957 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -254,8 +254,8 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { - return true, nil +func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { + return true, true, nil } func (g *gdprPerms) AMPException() bool { diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 8499ac1ca5d..3f47b257d2e 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -437,8 +437,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { - return g.allowPI, nil +func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { + return g.allowPI, g.allowPI, nil } func (g *mockPermsSetUID) AMPException() bool { diff --git a/exchange/utils.go b/exchange/utils.go index d961089c4cb..f602d1e8fba 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -60,10 +60,12 @@ func cleanOpenRTBRequests(ctx context.Context, coreBidder := resolveBidder(bidder.String(), aliases) var publisherID = labels.PubID - ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) + ok, geo, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) privacyEnforcement.GDPR = !ok && err == nil + privacyEnforcement.GDPRGeo = !geo && err == nil } else { privacyEnforcement.GDPR = false + privacyEnforcement.GDPRGeo = false } privacyEnforcement.Apply(bidReq, ampGDPRException) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 53d6b85c243..acbf25ff691 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -24,11 +24,11 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { +func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { if bidder == "appnexus" { - return true, nil + return true, true, nil } - return false, nil + return false, false, nil } func (p *permissionsMock) AMPException() bool { diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 9390d942f80..0dfa12f5ebd 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -23,7 +23,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) + PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) // Exposes the AMP execption flag AMPException() bool diff --git a/gdpr/impl.go b/gdpr/impl.go index 8743d7f2778..60db804aec6 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -2,11 +2,13 @@ package gdpr import ( "context" + "fmt" "github.com/prebid/go-gdpr/api" tcf1constants "github.com/prebid/go-gdpr/consentconstants" consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -40,10 +42,10 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { +func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { _, ok := p.cfg.NonStandardPublisherMap[PublisherID] if ok { - return true, nil + return true, true, nil } id, ok := p.vendorIDs[bidder] @@ -52,10 +54,10 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt } if consent == "" { - return p.cfg.UsersyncIfAmbiguous, nil + return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil } - return false, nil + return false, false, nil } func (p *permissionsImpl) AMPException() bool { @@ -78,38 +80,104 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen } // InfoStorageAccess is the same across TCF 1 and TCF 2 + if parsedConsent.Version() == 2 { + if !p.cfg.TCF2.Purpose1.Enabled { + // We are not enforcing purpose 1 + return true, nil + } + consent, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + err := fmt.Errorf("Unable to access TCF2 parsed consent") + return false, err + } + return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess), nil + } if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { return true, nil } return false, nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, error) { +func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, error) { // If we're not given a consent string, respect the preferences in the app config. if consent == "" { - return p.cfg.UsersyncIfAmbiguous, nil + return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil } parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { - return false, err + return false, false, err } if vendor == nil { - return false, nil + return false, false, nil } if parsedConsent.Version() == 2 { - // Need to add the location special purpose once the library supports it. + if p.cfg.TCF2.Enabled { + return p.allowPITCF2(parsedConsent, vendor, vendorID) + } if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) { - return true, nil + return true, true, nil } } else { if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, nil + return true, true, nil } } - return false, nil + return false, false, nil +} + +func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, err error) { + consent, ok := parsedConsent.(tcf2.ConsentMetadata) + err = nil + allowPI = false + allowGeo = false + if !ok { + err = fmt.Errorf("Unable to access TCF2 parsed consent") + return + } + if p.cfg.TCF2.SpecialPurpose1.Enabled { + allowGeo = consent.SpecialFeatureOptIn(1) && vendor.SpecialPurpose(1) + } else { + allowGeo = true + } + // Set to true so any purpose check can flip it to false + allowPI = true + if p.cfg.TCF2.Purpose1.Enabled { + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess) + } + if p.cfg.TCF2.Purpose2.Enabled { + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving) + } + if p.cfg.TCF2.Purpose7.Enabled { + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance) + } + return +} + +const pubRestrictNotAllowed = 0 +const pubRestrictRequireConsent = 1 +const pubRestrictRequireLegitInterest = 2 + +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose) bool { + if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { + return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed + } + if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { + return false + } + if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { + return vendor.PurposeStrict(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) + } + if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireLegitInterest, vendorID) { + // Need LITransparency here + return vendor.LegitimateInterestStrict(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + } + purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) + legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + + return purposeAllowed || legitInterest } func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) { @@ -146,8 +214,8 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { - return true, nil +func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { + return true, true, nil } func (a AlwaysAllow) AMPException() bool { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 8b89577d6c8..f05f25e87ea 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -10,6 +10,9 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/go-gdpr/vendorlist2" + + "github.com/stretchr/testify/assert" ) func TestNoConsentButAllowByDefault(t *testing.T) { @@ -55,10 +58,10 @@ func TestNoConsentAndRejectByDefault(t *testing.T) { func TestAllowedSyncs(t *testing.T) { vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ 2: { - purposes: []uint8{1}, + purposes: []int{1}, }, 3: { - purposes: []uint8{1}, + purposes: []int{1}, }, }) perms := permissionsImpl{ @@ -91,10 +94,10 @@ func TestAllowedSyncs(t *testing.T) { func TestProhibitedPurposes(t *testing.T) { vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ 2: { - purposes: []uint8{1}, // cookie reads/writes + purposes: []int{1}, // cookie reads/writes }, 3: { - purposes: []uint8{3}, // ad personalization + purposes: []int{3}, // ad personalization }, }) perms := permissionsImpl{ @@ -127,10 +130,10 @@ func TestProhibitedPurposes(t *testing.T) { func TestProhibitedVendors(t *testing.T) { vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ 2: { - purposes: []uint8{1}, // cookie reads/writes + purposes: []int{1}, // cookie reads/writes }, 3: { - purposes: []uint8{3}, // ad personalization + purposes: []int{3}, // ad personalization }, }) perms := permissionsImpl{ @@ -179,10 +182,10 @@ func TestMalformedConsent(t *testing.T) { func TestAllowPersonalInfo(t *testing.T) { vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ 2: { - purposes: []uint8{1}, // cookie reads/writes + purposes: []int{1}, // cookie reads/writes }, 3: { - purposes: []uint8{1, 3}, // ad personalization + purposes: []int{1, 3}, // ad personalization }, }) perms := permissionsImpl{ @@ -204,21 +207,377 @@ func TestAllowPersonalInfo(t *testing.T) { } // PI needs both purposes to succeed - allowPI, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, false, allowPI) - allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, true, allowPI) // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1} - allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, true, allowPI) } +var tcf2BasicPurposes = map[uint16]*purposes{ + 2: {purposes: []int{1}}, //cookie reads/writes + 6: {purposes: []int{1, 2, 4}}, // ad personalization + 8: {purposes: []int{1, 7}}, + 10: {purposes: []int{2, 4, 7}}, + 32: {purposes: []int{1, 2, 4, 7}}, +} +var tcf2LegitInterests = map[uint16]*purposes{ + 6: {purposes: []int{7}}, + 8: {purposes: []int{2, 4}}, +} +var tcf2SpecialPuproses = map[uint16]*purposes{ + 6: {purposes: []int{1}}, + 10: {purposes: []int{1}}, +} +var tcf2FlexPurposes = map[uint16]*purposes{ + 6: {purposes: []int{1, 2, 4, 7}}, +} +var tcf2Config = config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.PurposeDetail{Enabled: true}, + Purpose2: config.PurposeDetail{Enabled: true}, + Purpose7: config.PurposeDetail{Enabled: true}, + SpecialPurpose1: config.PurposeDetail{Enabled: true}, + }, +} + +type tcf2TestDef struct { + description string + bidder openrtb_ext.BidderName + consent string + allowPI bool + allowGeo bool +} + +func TestAllowPersonalInfoTCF2(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // PI needs all purposes to succeed + testDefs := []tcf2TestDef{ + { + description: "Appnexus vendor test, insufficient purposes claimed", + bidder: openrtb_ext.BidderAppnexus, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowPI: false, + allowGeo: false, + }, + { + description: "Pubmatic vendor test, flex purposes claimed", + bidder: openrtb_ext.BidderPubmatic, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowPI: true, + allowGeo: true, + }, + { + description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", + bidder: openrtb_ext.BidderRubicon, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowPI: true, + allowGeo: false, + }, + } + + for _, td := range testDefs { + allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) + assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + } +} + +func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array + perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1} + allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") + assert.EqualValuesf(t, true, allowPI, "AllowPI failure") + assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") + +} + +func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 32, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 15: parseVendorListDataV2(t, vendorListData), + }), + }, + } + + // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, + // Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent + testDefs := []tcf2TestDef{ + { + description: "Appnexus vendor test, insufficient purposes claimed", + bidder: openrtb_ext.BidderAppnexus, + consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", + allowPI: false, + allowGeo: false, + }, + { + description: "Pubmatic vendor test, flex purposes claimed", + bidder: openrtb_ext.BidderPubmatic, + consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", + allowPI: false, + allowGeo: false, + }, + { + description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", + bidder: openrtb_ext.BidderRubicon, + consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", + allowPI: false, + allowGeo: false, + }, + } + + for _, td := range testDefs { + allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) + assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + } +} + +func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 10, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.cfg.TCF2.PurposeOneTreatment.Enabled = true + perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = true + + // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set + testDefs := []tcf2TestDef{ + { + description: "Appnexus vendor test, insufficient purposes claimed", + bidder: openrtb_ext.BidderAppnexus, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: false, + allowGeo: false, + }, + { + description: "Pubmatic vendor test, flex purposes claimed", + bidder: openrtb_ext.BidderPubmatic, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: true, + allowGeo: true, + }, + { + description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", + bidder: openrtb_ext.BidderRubicon, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: true, + allowGeo: false, + }, + } + + for _, td := range testDefs { + allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) + assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + } +} + +func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 10, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.cfg.TCF2.PurposeOneTreatment.Enabled = true + perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false + + // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set + testDefs := []tcf2TestDef{ + { + description: "Appnexus vendor test, insufficient purposes claimed", + bidder: openrtb_ext.BidderAppnexus, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: false, + allowGeo: false, + }, + { + description: "Pubmatic vendor test, flex purposes claimed", + bidder: openrtb_ext.BidderPubmatic, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: false, + allowGeo: true, + }, + { + description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", + bidder: openrtb_ext.BidderRubicon, + consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", + allowPI: false, + allowGeo: false, + }, + } + + for _, td := range testDefs { + allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) + assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + } +} + +func TestAllowSyncTCF2(t *testing.T) { + vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") + assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") + + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") + assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") +} + +func TestProhibitedPurposeSyncTCF2(t *testing.T) { + basicPurposes := tcf2BasicPurposes + basicPurposes[8] = &purposes{purposes: []int{7}} + vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.cfg.HostVendorID = 8 + + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") + assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") + + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") + assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") +} + +func TestProhibitedVendorSyncTCF2(t *testing.T) { + basicPurposes := tcf2BasicPurposes + basicPurposes[10] = &purposes{purposes: []int{1}} + vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + perms := permissionsImpl{ + cfg: tcf2Config, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 10, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tCF1: nil, + tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.cfg.HostVendorID = 10 + + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 4, 6 + allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") + assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") + + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") + assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") +} + func parseVendorListData(t *testing.T, data string) vendorlist.VendorList { t.Helper() parsed, err := vendorlist.ParseEagerly([]byte(data)) @@ -228,6 +587,15 @@ func parseVendorListData(t *testing.T, data string) vendorlist.VendorList { return parsed } +func parseVendorListDataV2(t *testing.T, data string) vendorlist.VendorList { + t.Helper() + parsed, err := vendorlist2.ParseEagerly([]byte(data)) + if err != nil { + t.Fatalf("Failed to parse vendor list data. %v", err) + } + return parsed +} + func listFetcher(lists map[uint16]vendorlist.VendorList) func(context.Context, uint16) (vendorlist.VendorList, error) { return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { data, ok := lists[id] diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 8197fa263bc..824f9178faa 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -15,12 +15,12 @@ import ( func TestVendorFetch(t *testing.T) { vendorListOne := mockVendorListData(t, 1, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2}, + purposes: []int{1, 2}, }, }) vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2, 3}, + purposes: []int{1, 2, 3}, }, }) server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ @@ -47,12 +47,12 @@ func TestVendorFetch(t *testing.T) { func TestLazyFetch(t *testing.T) { firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2}, + purposes: []int{1, 2}, }, }) secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ 3: { - purposes: []uint8{1}, + purposes: []int{1}, }, }) server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ @@ -73,7 +73,7 @@ func TestLazyFetch(t *testing.T) { func TestInitialTimeout(t *testing.T) { list := mockVendorListData(t, 1, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2}, + purposes: []int{1, 2}, }, }) server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ @@ -91,12 +91,12 @@ func TestInitialTimeout(t *testing.T) { func TestFetchThrottling(t *testing.T) { vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2}, + purposes: []int{1, 2}, }, }) vendorListThree := mockVendorListData(t, 3, map[uint16]*purposes{ 32: { - purposes: []uint8{1, 2}, + purposes: []int{1, 2}, }, }) server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ @@ -174,8 +174,8 @@ func mockServer(latestVersion int, responses map[int]string) func(http.ResponseW func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purposes) string { type vendorContract struct { - ID uint16 `json:"id"` - Purposes []uint8 `json:"purposeIds"` + ID uint16 `json:"id"` + Purposes []int `json:"purposeIds"` } type vendorListContract struct { @@ -203,6 +203,72 @@ func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purpos return string(data) } +type purposeMap map[uint16]*purposes + +func mockVendorListDataTCF2(t *testing.T, version uint16, basicPurposes purposeMap, legitInterests purposeMap, flexPurposes purposeMap, specialPurposes purposeMap) string { + type vendorContract struct { + ID uint16 `json:"id"` + Purposes []int `json:"purposes"` + LegIntPurposes []int `json:"legIntPurposes"` + FlexiblePurposes []int `json:"flexiblePurposes"` + SpecialPurposes []int `json:"specialPurposes"` + } + + type vendorListContract struct { + Version uint16 `json:"vendorListVersion"` + Vendors map[string]vendorContract `json:"vendors"` + } + + vendors := make(map[string]vendorContract, len(basicPurposes)) + for id, purpose := range basicPurposes { + sid := strconv.Itoa(int(id)) + vendor, ok := vendors[sid] + if !ok { + vendor = vendorContract{ID: id} + } + vendor.Purposes = purpose.purposes + vendors[sid] = vendor + } + + for id, purpose := range legitInterests { + sid := strconv.Itoa(int(id)) + vendor, ok := vendors[sid] + if !ok { + vendor = vendorContract{ID: id} + } + vendor.LegIntPurposes = purpose.purposes + vendors[sid] = vendor + } + + for id, purpose := range flexPurposes { + sid := strconv.Itoa(int(id)) + vendor, ok := vendors[sid] + if !ok { + vendor = vendorContract{ID: id} + } + vendor.FlexiblePurposes = purpose.purposes + vendors[sid] = vendor + } + + for id, purpose := range specialPurposes { + sid := strconv.Itoa(int(id)) + vendor, ok := vendors[sid] + if !ok { + vendor = vendorContract{ID: id} + } + vendor.SpecialPurposes = purpose.purposes + vendors[sid] = vendor + } + + obj := vendorListContract{ + Version: version, + Vendors: vendors, + } + data, err := json.Marshal(obj) + assertNilErr(t, err) + return string(data) +} + func testURLMaker(server *httptest.Server) func(uint16, uint8) string { url := server.URL return func(version uint16, TCFVer uint8) string { @@ -220,5 +286,5 @@ func testConfig() config.GDPR { } type purposes struct { - purposes []uint8 + purposes []int } diff --git a/go.mod b/go.mod index 8de6f10e4b9..0224057e464 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,8 @@ require ( github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pelletier/go-toml v1.2.0 // indirect - github.com/prebid/go-gdpr v0.7.0 + github.com/prebid/go-gdpr v0.8.2 + github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect diff --git a/go.sum b/go.sum index 176bacfc20a..5d941b89e90 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,10 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prebid/go-gdpr v0.7.0 h1:m4E/FjUhTBMciDsd3lQlbzFyXLzNK+JQkFmInJpFAwc= -github.com/prebid/go-gdpr v0.7.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= +github.com/prebid/go-gdpr v0.8.2 h1:mN2jKYZZpJkCYFQB/nDTJoPpuGYblOYP2UUzOzRggII= +github.com/prebid/go-gdpr v0.8.2/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= +github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= +github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 96d03ef4433..d302192ec3f 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -6,14 +6,15 @@ import ( // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { - CCPA bool - COPPA bool - GDPR bool + CCPA bool + COPPA bool + GDPR bool + GDPRGeo bool } // Any returns true if at least one privacy policy requires enforcement. func (e Enforcement) Any() bool { - return e.CCPA || e.COPPA || e.GDPR + return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo } // Apply cleans personally identifiable information from an OpenRTB bid request. @@ -45,7 +46,7 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { return ScrubStrategyGeoFull } - if e.GDPR || e.CCPA { + if e.GDPRGeo || e.CCPA { return ScrubStrategyGeoReducedPrecision } @@ -60,5 +61,11 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs if e.GDPR && ampGDPRException { return ScrubStrategyUserNone } - return ScrubStrategyUserID + + // If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above) + if e.CCPA || e.GDPR { + return ScrubStrategyUserID + } + + return ScrubStrategyUserNone } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 25e08b5e80d..0e82648d4b9 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -17,27 +17,40 @@ func TestAny(t *testing.T) { { description: "All False", enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPR: false, + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: false, }, expected: false, }, { description: "All True", enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPR: true, + CCPA: true, + COPPA: true, + GDPR: true, + GDPRGeo: true, }, expected: true, }, { description: "Mixed", enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPR: false, + CCPA: false, + COPPA: true, + GDPR: false, + GDPRGeo: false, + }, + expected: true, + }, + { + description: "GDPRGeo only", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: true, }, expected: true, }, @@ -62,9 +75,10 @@ func TestApply(t *testing.T) { { description: "All Enforced", enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPR: true, + CCPA: true, + COPPA: true, + GDPR: true, + GDPRGeo: true, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -75,9 +89,10 @@ func TestApply(t *testing.T) { { description: "CCPA Only", enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: false, + CCPA: true, + COPPA: false, + GDPR: false, + GDPRGeo: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -88,9 +103,10 @@ func TestApply(t *testing.T) { { description: "COPPA Only", enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPR: false, + CCPA: false, + COPPA: true, + GDPR: false, + GDPRGeo: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -101,9 +117,10 @@ func TestApply(t *testing.T) { { description: "GDPR Only", enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPR: true, + CCPA: false, + COPPA: false, + GDPR: true, + GDPRGeo: true, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -114,9 +131,10 @@ func TestApply(t *testing.T) { { description: "GDPR Only, ampGDPRException", enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPR: true, + CCPA: false, + COPPA: false, + GDPR: true, + GDPRGeo: true, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -127,9 +145,10 @@ func TestApply(t *testing.T) { { description: "CCPA Only, ampGDPRException", enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: false, + CCPA: true, + COPPA: false, + GDPR: false, + GDPRGeo: false, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -140,9 +159,10 @@ func TestApply(t *testing.T) { { description: "COPPA and GDPR, ampGDPRException", enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPR: true, + CCPA: false, + COPPA: true, + GDPR: true, + GDPRGeo: true, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -150,6 +170,34 @@ func TestApply(t *testing.T) { expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoFull, }, + { + description: "GDPR Only, no Geo", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: true, + GDPRGeo: false, + }, + ampGDPRException: false, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceGeo: ScrubStrategyGeoNone, + expectedUser: ScrubStrategyUserID, + expectedUserGeo: ScrubStrategyGeoNone, + }, + { + description: "GDPR Only, Geo only", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: true, + }, + ampGDPRException: false, + expectedDeviceIPv6: ScrubStrategyIPV6None, + expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedUser: ScrubStrategyUserNone, + expectedUserGeo: ScrubStrategyGeoReducedPrecision, + }, } for _, test := range testCases { From 23c684c5a6c4189ca172a83453b2cfa8de2bf0a5 Mon Sep 17 00:00:00 2001 From: Seba Perez Date: Wed, 3 Jun 2020 15:40:46 -0300 Subject: [PATCH 104/603] Change on eplanning endpoint (hostname) (#1328) --- adapters/eplanning/eplanning_test.go | 2 +- adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json | 2 +- adapters/eplanning/eplanningtest/exemplary/simple-banner.json | 2 +- adapters/eplanning/eplanningtest/exemplary/two-banners.json | 2 +- .../supplemental/app-domain-and-url-correctly-parsed.json | 2 +- .../eplanningtest/supplemental/banner-no-size-sends-1x1.json | 2 +- .../eplanningtest/supplemental/invalid-response-no-bids.json | 2 +- .../supplemental/invalid-response-unmarshall-error.json | 2 +- .../eplanningtest/supplemental/server-bad-request.json | 2 +- .../eplanning/eplanningtest/supplemental/server-error-code.json | 2 +- .../eplanning/eplanningtest/supplemental/server-no-content.json | 2 +- .../supplemental/site-domain-and-url-correctly-parsed.json | 2 +- config/config.go | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index 461fb849ced..28fdf6c45c2 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -8,7 +8,7 @@ import ( ) func TestJsonSamples(t *testing.T) { - eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/pbs/1") + eplanningAdapter := NewEPlanningBidder(new(http.Client), "http://rtb.e-planning.net/pbs/1") eplanningAdapter.testing = true adapterstest.RunJSONBidderTest(t, "eplanningtest", eplanningAdapter) } diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json index a67a86d18e6..556831217ec 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json index 9146bb4afb5..04eca985340 100644 --- a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json +++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&uid=2154987&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&uid=2154987&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/exemplary/two-banners.json b/adapters/eplanning/eplanningtest/exemplary/two-banners.json index 174c8ce3fc6..72aeb64b3a9 100644 --- a/adapters/eplanning/eplanningtest/exemplary/two-banners.json +++ b/adapters/eplanning/eplanningtest/exemplary/two-banners.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json index 04df82f6668..413c973dfa2 100644 --- a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json +++ b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json index f02eb80fe41..05547d81707 100644 --- a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json +++ b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json index 8bdcfddd733..570488825e2 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json index 9f5b2d7fc03..4ba2d44bf3a 100644 --- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json index 2ef03648884..9e8eae8c080 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json index 76e75a5c203..08f46d9e6c2 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json index 02f1fa46d33..20a2b1cf456 100644 --- a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json +++ b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json @@ -21,7 +21,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE", "body": {} }, "mockResponse": { diff --git a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json index 581cb1d5b46..62890d914ff 100644 --- a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json +++ b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads.us.e-planning.net/pbs/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere", + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere", "body": {} }, "mockResponse": { diff --git a/config/config.go b/config/config.go index 5f19629d2db..9652ae141f5 100755 --- a/config/config.go +++ b/config/config.go @@ -739,7 +739,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") - v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/pbs/1") + v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") From 352784573cbdb29d725c45328dda7fa335096116 Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Wed, 3 Jun 2020 14:43:31 -0400 Subject: [PATCH 105/603] Districtm Dmx: new adapter (#1209) Co-authored-by: steve-a-districtm --- adapters/dmx/dmx.go | 296 +++++++ adapters/dmx/dmx_test.go | 782 ++++++++++++++++++ .../dmx/dmxtest/exemplary/simple-app.json | 138 ++++ .../dmx/dmxtest/exemplary/simple-banner.json | 126 +++ .../dmx/dmxtest/exemplary/simple-video.json | 112 +++ adapters/dmx/dmxtest/params/race/banner.json | 4 + adapters/dmx/dmxtest/params/race/video.json | 4 + adapters/dmx/usersync.go | 12 + adapters/dmx/usersync_test.go | 20 + config/config.go | 4 +- exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/dmx.yaml | 11 + static/bidder-params/dmx.json | 22 + usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 16 files changed, 1537 insertions(+), 1 deletion(-) create mode 100644 adapters/dmx/dmx.go create mode 100644 adapters/dmx/dmx_test.go create mode 100644 adapters/dmx/dmxtest/exemplary/simple-app.json create mode 100644 adapters/dmx/dmxtest/exemplary/simple-banner.json create mode 100644 adapters/dmx/dmxtest/exemplary/simple-video.json create mode 100644 adapters/dmx/dmxtest/params/race/banner.json create mode 100644 adapters/dmx/dmxtest/params/race/video.json create mode 100644 adapters/dmx/usersync.go create mode 100644 adapters/dmx/usersync_test.go create mode 100644 static/bidder-info/dmx.yaml create mode 100644 static/bidder-params/dmx.json diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go new file mode 100644 index 00000000000..6b4f698d4b1 --- /dev/null +++ b/adapters/dmx/dmx.go @@ -0,0 +1,296 @@ +package dmx + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "net/url" + "strings" +) + +type DmxAdapter struct { + endpoint string +} + +func NewDmxBidder(endpoint string) *DmxAdapter { + return &DmxAdapter{endpoint: endpoint} +} + +type dmxExt struct { + Bidder dmxParams `json:"bidder"` +} + +type dmxParams struct { + TagId string `json:"tagid,omitempty"` + DmxId string `json:"dmxid,omitempty"` + MemberId string `json:"memberid,omitempty"` + PublisherId string `json:"publisher_id,omitempty"` + SellerId string `json:"seller_id,omitempty"` +} + +func UserSellerOrPubId(str1, str2 string) string { + if str1 != "" { + return str1 + } + return str2 +} + +func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { + var imps []openrtb.Imp + var rootExtInfo dmxExt + var publisherId string + var sellerId string + var userExt openrtb_ext.ExtUser + var anyHasId = false + var reqCopy openrtb.BidRequest = *request + var dmxReq *openrtb.BidRequest = &reqCopy + + if request.User == nil { + if request.App == nil { + return nil, []error{errors.New("No user id or app id found. Could not send request to DMX.")} + } + } + + if len(request.Imp) >= 1 { + err := json.Unmarshal(request.Imp[0].Ext, &rootExtInfo) + if err != nil { + errs = append(errs, err) + } else { + publisherId = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId) + sellerId = rootExtInfo.Bidder.SellerId + } + } + + if request.App != nil { + appCopy := *request.App + appPublisherCopy := *request.App.Publisher + dmxReq.App = &appCopy + dmxReq.App.Publisher = &appPublisherCopy + if dmxReq.App.ID != "" { + anyHasId = true + } + } else { + dmxReq.App = nil + } + + if request.Site != nil { + siteCopy := *request.Site + sitePublisherCopy := *request.Site.Publisher + dmxReq.Site = &siteCopy + dmxReq.Site.Publisher = &sitePublisherCopy + if dmxReq.Site.Publisher != nil { + dmxReq.Site.Publisher.ID = publisherId + } else { + dmxReq.Site.Publisher = &openrtb.Publisher{ID: publisherId} + } + } else { + dmxReq.Site = nil + } + + if request.User != nil { + userCopy := *request.User + dmxReq.User = &userCopy + } else { + dmxReq.User = nil + } + + if dmxReq.User != nil { + if dmxReq.User.ID != "" { + anyHasId = true + } + if dmxReq.User.Ext != nil { + if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil { + if len(userExt.Eids) > 0 || (userExt.DigiTrust != nil && userExt.DigiTrust.ID != "") { + anyHasId = true + } + } + } + } + + if anyHasId == false { + return nil, []error{errors.New("This request contained no identifier")} + } + + for _, inst := range dmxReq.Imp { + var banner *openrtb.Banner + var video *openrtb.Video + var ins openrtb.Imp + var params dmxExt + const intVal int8 = 1 + source := (*json.RawMessage)(&inst.Ext) + if err := json.Unmarshal(*source, ¶ms); err != nil { + errs = append(errs, err) + } + if isDmxParams(params.Bidder) { + if inst.Banner != nil { + if len(inst.Banner.Format) != 0 { + banner = inst.Banner + if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { + imps = fetchParams(params, inst, ins, imps, banner, nil, intVal) + } else { + return nil, []error{errors.New("Missing Params for auction to be send")} + } + } + } + + if inst.Video != nil { + video = inst.Video + if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { + imps = fetchParams(params, inst, ins, imps, nil, video, intVal) + } else { + return nil, []error{errors.New("Missing Params for auction to be send")} + } + } + } + + } + + dmxReq.Imp = imps + + oJson, err := json.Marshal(dmxReq) + + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "Application/json;charset=utf-8") + reqBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint + addParams(sellerId), //adapter.endpoint, + Body: oJson, + Headers: headers, + } + reqsBidder = append(reqsBidder, reqBidder) + return +} + +func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if http.StatusNoContent == response.StatusCode { + return nil, nil + } + + if http.StatusBadRequest == response.StatusCode { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code 400"), + }} + } + + if http.StatusOK != response.StatusCode { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected response no status code"), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + if b.BidType == openrtb_ext.BidTypeVideo { + b.Bid.AdM = videoImpInsertion(b.Bid) + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs + +} + +func fetchParams(params dmxExt, inst openrtb.Imp, ins openrtb.Imp, imps []openrtb.Imp, banner *openrtb.Banner, video *openrtb.Video, intVal int8) []openrtb.Imp { + if params.Bidder.TagId != "" { + ins = openrtb.Imp{ + ID: inst.ID, + TagID: params.Bidder.TagId, + Ext: inst.Ext, + Secure: &intVal, + } + } + + if params.Bidder.DmxId != "" { + ins = openrtb.Imp{ + ID: inst.ID, + TagID: params.Bidder.DmxId, + Ext: inst.Ext, + Secure: &intVal, + } + } + if banner != nil { + ins.Banner = banner + } + + if video != nil { + ins.Video = video + } + + if ins.TagID == "" { + return imps + } + imps = append(imps, ins) + return imps +} + +func addParams(str string) string { + if str != "" { + return "?sellerid=" + url.QueryEscape(str) + } + return "" +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType, nil + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + } +} + +func videoImpInsertion(bid *openrtb.Bid) string { + adm := bid.AdM + nurl := bid.NURL + search := "" + imp := "" + wrapped_nurl := fmt.Sprintf(imp, nurl) + results := strings.Replace(adm, search, wrapped_nurl, 1) + return results +} + +func isDmxParams(t interface{}) bool { + switch t.(type) { + case dmxParams: + return true + default: + return false + } +} diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go new file mode 100644 index 00000000000..e9f195eb61d --- /dev/null +++ b/adapters/dmx/dmx_test.go @@ -0,0 +1,782 @@ +package dmx + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "strings" + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +var ( + bidRequest string +) + +func TestFetchParams(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + var arrImp []openrtb.Imp + var imps = fetchParams( + dmxExt{Bidder: dmxParams{ + TagId: "222", + PublisherId: "5555", + }}, + openrtb.Imp{ID: "32"}, + openrtb.Imp{ID: "32"}, + arrImp, + &openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{ + {W: 300, H: 250}, + }}, + nil, + 1) + var imps2 = fetchParams( + dmxExt{Bidder: dmxParams{ + DmxId: "222", + MemberId: "5555", + }}, + openrtb.Imp{ID: "32"}, + openrtb.Imp{ID: "32"}, + arrImp, + &openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{ + {W: 300, H: 250}, + }}, + nil, + 1) + if len(imps) == 0 { + t.Errorf("should increment the length by one") + } + + if len(imps2) == 0 { + t.Errorf("should increment the length by one") + } + +} +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "dmxtest", new(DmxAdapter)) +} + +func TestMakeRequestsOtherPlacement(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + User: &openrtb.User{ID: "bscakucbkasucbkasunscancasuin"}, + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + ID: "1234", + } + + actualAdapterRequests, err := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if actualAdapterRequests == nil { + t.Errorf("request should be nil") + } + if len(err) != 0 { + t.Errorf("We should have no error") + } + +} + +func TestMakeRequestsInvalid(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + ID: "1234", + } + + actualAdapterRequests, err := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if len(actualAdapterRequests) != 0 { + t.Errorf("request should be nil") + } + if len(err) == 0 { + t.Errorf("We should have no error") + } + +} + +func TestMakeRequestNoSite(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if len(actualAdapterRequests) != 1 { + t.Errorf("openrtb type should be an Array when it's an App") + } + var the_body openrtb.BidRequest + if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { + t.Errorf("failed to read bid request") + } + + if the_body.App == nil { + t.Errorf("app property should be populated") + } + + if the_body.App.Publisher.ID == "" { + t.Errorf("Missing publisher ID must be in") + } +} + +func TestMakeRequestsApp(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if len(actualAdapterRequests) != 1 { + t.Errorf("openrtb type should be an Array when it's an App") + } + var the_body openrtb.BidRequest + if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { + t.Errorf("failed to read bid request") + } + + if the_body.App == nil { + t.Errorf("app property should be populated") + } + +} + +func TestMakeRequestsNoUser(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if actualAdapterRequests != nil { + t.Errorf("openrtb type should be empty") + } + +} + +func TestMakeRequests(t *testing.T) { + //server := httptest.NewServer(http.HandlerFunc(DummyDmxServer)) + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + imp2 := openrtb.Imp{ + ID: "imp2", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + imp3 := openrtb.Imp{ + ID: "imp3", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1, imp2, imp3}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{ID: "districtmID"}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if len(actualAdapterRequests) != 1 { + t.Errorf("should have 1 request") + } + var the_body openrtb.BidRequest + if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { + t.Errorf("failed to read bid request") + } + + if len(the_body.Imp) != 3 { + t.Errorf("must have 3 bids") + } + +} + +func TestMakeBidVideo(t *testing.T) { + var w, h int = 640, 480 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Video: &openrtb.Video{ + W: width, + H: height, + MIMEs: []string{"video/mp4"}, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{ID: "districtmID"}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + if len(actualAdapterRequests) != 1 { + t.Errorf("should have 1 request") + } + var the_body openrtb.BidRequest + if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { + t.Errorf("failed to read bid request") + } + + if len(the_body.Imp) != 1 { + t.Errorf("must have 1 bids") + } +} + +func TestMakeBidsNoContent(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{ID: "districtmID"}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + + _, err204 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 204}) + + if err204 != nil { + t.Errorf("Was expecting nil") + } + + _, err400 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 400}) + + if err400 == nil { + t.Errorf("Was expecting error") + } + + _, err500 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 500}) + + if err500 == nil { + t.Errorf("Was expecting error") + } + + bidResponse := &adapters.ResponseData{ + StatusCode: 200, + Body: []byte(`{ + "id": "JdSgvXjee0UZ", + "seatbid": [ + { + "bid": [ + { + "id": "16-40dbf1ef_0gKywr9JnzPAW4bE-1", + "impid": "imp1", + "price": 2.3456, + "adm": "", + "nurl": "dmxnotificationurlhere", + "adomain": [ + "brand.com", + "advertiser.net" + ], + "cid": "12345", + "crid": "232303", + "cat": [ + "IAB20-3" + ], + "attr": [ + 2 + ], + "w": 300, + "h": 600, + "language": "en" + } + ], + "seat": "10001" + } + ], + "cur": "USD" +}`), + } + + bidResponseNoMatch := &adapters.ResponseData{ + StatusCode: 200, + Body: []byte(`{ + "id": "JdSgvXjee0UZ", + "seatbid": [ + { + "bid": [ + { + "id": "16-40dbf1ef_0gKywr9JnzPAW4bE-1", + "impid": "djvnsvns", + "price": 2.3456, + "adm": "", + "nurl": "dmxnotificationurlhere", + "adomain": [ + "brand.com", + "advertiser.net" + ], + "cid": "12345", + "crid": "232303", + "cat": [ + "IAB20-3" + ], + "attr": [ + 2 + ], + "w": 300, + "h": 600, + "language": "en" + } + ], + "seat": "10001" + } + ], + "cur": "USD" +}`), + } + + bids, _ := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], bidResponse) + if bids == nil { + t.Errorf("ads not parse") + } + bidsNoMatching, _ := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], bidResponseNoMatch) + if bidsNoMatching == nil { + t.Errorf("ads not parse") + } + +} +func TestUserExtEmptyObject(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1, imp1, imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{Ext: json.RawMessage(`{}`)}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + if len(actualAdapterRequests) != 0 { + t.Errorf("should have 0 request") + } +} +func TestUserEidsOnly(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1, imp1, imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{Ext: json.RawMessage(`{"eids": [{ + "source": "adserver.org", + "uids": [{ + "id": "111111111111", + "ext": { + "rtiPartner": "TDID" + } + }] + },{ + "source": "netid.de", + "uids": [{ + "id": "11111111" + }] + }] + }`)}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + if len(actualAdapterRequests) != 1 { + t.Errorf("should have 1 request") + } +} + +func TestUserDigitrustOnly(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1, imp1, imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{Ext: json.RawMessage(`{ + "digitrust": { + "id": "11111111111", + "keyv": 4 + }}`)}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + if len(actualAdapterRequests) != 1 { + t.Errorf("should have 1 request") + } +} + +func TestUsersEids(t *testing.T) { + var w, h int = 300, 250 + + var width, height uint64 = uint64(w), uint64(h) + + adapter := NewDmxBidder("https://dmx.districtm.io/b/v2") + imp1 := openrtb.Imp{ + ID: "imp1", + Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), + Banner: &openrtb.Banner{ + W: &width, + H: &height, + Format: []openrtb.Format{ + {W: 300, H: 250}, + }, + }} + + inputRequest := openrtb.BidRequest{ + Imp: []openrtb.Imp{imp1, imp1, imp1}, + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "10007", + }, + }, + User: &openrtb.User{ID: "districtmID", Ext: json.RawMessage(`{"eids": [{ + "source": "adserver.org", + "uids": [{ + "id": "111111111111", + "ext": { + "rtiPartner": "TDID" + } + }] + },{ + "source": "pubcid.org", + "uids": [{ + "id":"11111111" + }] + }, + { + "source": "id5-sync.com", + "uids": [{ + "id": "ID5-12345" + }] + }, + { + "source": "parrable.com", + "uids": [{ + "id": "01.1563917337.test-eid" + }] + },{ + "source": "identityLink", + "uids": [{ + "id": "11111111" + }] + },{ + "source": "criteo", + "uids": [{ + "id": "11111111" + }] + },{ + "source": "britepool.com", + "uids": [{ + "id": "11111111" + }] + },{ + "source": "liveintent.com", + "uids": [{ + "id": "11111111" + }] + },{ + "source": "netid.de", + "uids": [{ + "id": "11111111" + }] + }], + "digitrust": { + "id": "11111111111", + "keyv": 4 + }}`)}, + ID: "1234", + } + + actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) + if len(actualAdapterRequests) != 1 { + t.Errorf("should have 1 request") + } + var the_body openrtb.BidRequest + if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { + t.Errorf("failed to read bid request") + } + + if len(the_body.Imp) != 3 { + t.Errorf("must have 3 bids") + } +} +func TestVideoImpInsertion(t *testing.T) { + var bidResp openrtb.BidResponse + var bid openrtb.Bid + payload := []byte(`{ + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "video1", + "impid": "video1", + "price": 5.01, + "nurl": "https://demo.arripiblik.com/359585167267151", + "adm": "BidSwitch", + "crid": "76575664756", + "dealid": "dmx-deal-hp-24", + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + { + "id": "some-impression-id", + "impid": "some-impression-id", + "price": 5.01, + "adm": "", + "crid": "1346943998", + "dealid": "dmx-deal-hp-24", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + { + "id": "some-impression-id2", + "impid": "some-impression-id2", + "price": 5.01, + "adm": "", + "crid": "1424798162", + "dealid": "dmx-deal-hp-24", + "w": 728, + "h": 90, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "dmx" + } + ] +}`) + + err := json.Unmarshal(payload, &bidResp) + if err != nil { + t.Errorf("Payload is invalid") + } + bid = openrtb.Bid(bidResp.SeatBid[0].Bid[0]) + data := videoImpInsertion(&bid) + find := strings.Index(data, "demo.arripiblik.com") + if find == -1 { + t.Errorf("String was not found") + } + +} diff --git a/adapters/dmx/dmxtest/exemplary/simple-app.json b/adapters/dmx/dmxtest/exemplary/simple-app.json new file mode 100644 index 00000000000..a2d57163f3c --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/simple-app.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "dmxid": "123454", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123454", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisher_id": "10400", + "dmxid": "123454" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adid": "29681110", + "adm": "
banner-ads
", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adm": "
banner-ads
", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/exemplary/simple-banner.json b/adapters/dmx/dmxtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..03ea6246ee4 --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/simple-banner.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "fhacacnasicnaic" + }, + "imp": [ + { + + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "dmxid": "123454", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "user": { + "id": "fhacacnasicnaic" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123454", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisher_id": "10400", + "dmxid": "123454" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adid": "29681110", + "adm": "
banner-ads
", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adm": "
banner-ads
", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/exemplary/simple-video.json b/adapters/dmx/dmxtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b4c53188119 --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/simple-video.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "whateveryouwant" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "tagid": "12345", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "user": { + "id": "whateveryouwant" + }, + "imp": [ + { + "ext": { + "bidder": { + "tagid": "12345", + "publisher_id": "10400" + } + }, + "id": "test-imp-id", + "tagid": "12345", + "secure": 1, + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 940, + "h": 560 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.90, + "adid": "29681110", + "adm": "ads", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.90, + "adm": "ads", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/params/race/banner.json b/adapters/dmx/dmxtest/params/race/banner.json new file mode 100644 index 00000000000..1c0adff78ac --- /dev/null +++ b/adapters/dmx/dmxtest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "tagid": "25251", + "publisher_id": "100152" +} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/params/race/video.json b/adapters/dmx/dmxtest/params/race/video.json new file mode 100644 index 00000000000..3bbd83bd3b0 --- /dev/null +++ b/adapters/dmx/dmxtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "tagid": "25255", + "publisher_id": "100151" +} \ No newline at end of file diff --git a/adapters/dmx/usersync.go b/adapters/dmx/usersync.go new file mode 100644 index 00000000000..98e56234fa6 --- /dev/null +++ b/adapters/dmx/usersync.go @@ -0,0 +1,12 @@ +package dmx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewDmxSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("dmx", 144, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/dmx/usersync_test.go b/adapters/dmx/usersync_test.go new file mode 100644 index 00000000000..e4e3c7d8e55 --- /dev/null +++ b/adapters/dmx/usersync_test.go @@ -0,0 +1,20 @@ +package dmx + +import ( + "github.com/prebid/prebid-server/privacy" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" +) + +func TestDmxSyncer(t *testing.T) { + temp := template.Must(template.New("sync-template").Parse("https://dmx.districtm.io/s/v1/img/s/10007")) + syncer := NewDmxSyncer(temp) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + assert.NoError(t, err) + assert.Equal(t, "https://dmx.districtm.io/s/v1/img/s/10007", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 144, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 9652ae141f5..e93aed46eab 100755 --- a/config/config.go +++ b/config/config.go @@ -534,6 +534,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -711,9 +712,10 @@ func SetupViper(v *viper.Viper, filename string) { // for them and specify all the parameters they need for them to work correctly. v.SetDefault("adapters.audiencenetwork.disabled", true) v.SetDefault("adapters.rubicon.disabled", true) - v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") v.SetDefault("adapters.33across.partner_id", "") + v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2") + v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index b69b5b50e13..390016117fb 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -29,6 +29,7 @@ import ( "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" + "github.com/prebid/prebid-server/adapters/dmx" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -107,6 +108,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), + openrtb_ext.BidderDmx: dmx.NewDmxBidder(cfg.Adapters[string(openrtb_ext.BidderDmx)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8a53e4adcf2..b3ecddb06cd 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -46,6 +46,7 @@ const ( BidderConversant BidderName = "conversant" BidderCpmstar BidderName = "cpmstar" BidderDatablocks BidderName = "datablocks" + BidderDmx BidderName = "dmx" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" BidderEPlanning BidderName = "eplanning" @@ -122,6 +123,7 @@ var BidderMap = map[string]BidderName{ "conversant": BidderConversant, "cpmstar": BidderCpmstar, "datablocks": BidderDatablocks, + "dmx": BidderDmx, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, "eplanning": BidderEPlanning, diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml new file mode 100644 index 00000000000..d6e54178db4 --- /dev/null +++ b/static/bidder-info/dmx.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "steve@districtm.net" +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/dmx.json b/static/bidder-params/dmx.json new file mode 100644 index 00000000000..4c0df65e3d4 --- /dev/null +++ b/static/bidder-params/dmx.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "District M DMX Adapter Params", + "description": "A schema which validates params accepted by the DMX adapter", + "type": "object", + "properties": { + "memberid" : { + "type": "string", + "description": "Represent boost MemberId from districtm UI" + }, + "tagid": { + "type": "string", + "description": "Represent the placement ID, this value is optional" + }, + "bidfloor": { + "type": "string", + "description": "The minimum price acceptable for a bid" + } + }, + + "required": ["memberid"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 791a00de0a9..5dccf855add 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -24,6 +24,7 @@ import ( "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" + "github.com/prebid/prebid-server/adapters/dmx" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -94,6 +95,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 9aae284da2a..ddd067e8be7 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -32,6 +32,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderConversant): syncConfig, string(openrtb_ext.BidderCpmstar): syncConfig, string(openrtb_ext.BidderDatablocks): syncConfig, + string(openrtb_ext.BidderDmx): syncConfig, string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig, string(openrtb_ext.BidderEPlanning): syncConfig, From b10b55ce107f1f217b5476367129aaf528350990 Mon Sep 17 00:00:00 2001 From: hbanalytics <55453525+hbanalytics@users.noreply.github.com> Date: Thu, 4 Jun 2020 20:18:34 +0300 Subject: [PATCH 106/603] Fix sync url for Yieldone s2s Bid Adapter (#1336) * Fix typo in Yieldone sync url --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index e93aed46eab..4e54bc712a2 100755 --- a/config/config.go +++ b/config/config.go @@ -575,7 +575,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderVrtcal doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_sc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") } From dc9d246285fea7b5fe13ebdb4a5e2992b0cbbead Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 8 Jun 2020 17:34:21 -0400 Subject: [PATCH 107/603] CCPA Video Bug (#1333) --- endpoints/openrtb2/amp_auction_test.go | 3 +- endpoints/openrtb2/auction.go | 7 +- endpoints/openrtb2/auction_test.go | 12 +- .../video/video_invalid_sample.json | 125 +++++++------- .../video/video_valid_sample.json | 155 ++++++++--------- .../video_valid_sample_ccpa_malformed.json | 88 ++++++++++ .../video/video_valid_sample_ccpa_valid.json | 88 ++++++++++ ...ideo_valid_sample_different_durations.json | 159 +++++++++--------- ...o_valid_sample_with_device_user_agent.json | 155 ++++++++--------- ...alid_sample_without_device_user_agent.json | 122 +++++++------- endpoints/openrtb2/video_auction.go | 4 +- endpoints/openrtb2/video_auction_test.go | 101 ++++++++++- privacy/ccpa/policy.go | 37 +++- privacy/ccpa/policy_test.go | 42 +++++ 14 files changed, 729 insertions(+), 369 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json create mode 100644 endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 9dc81eb1b9d..289db3f48cb 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -122,9 +122,8 @@ func TestAMPPageInfo(t *testing.T) { } func TestGDPRConsent(t *testing.T) { - consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" + consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" - digitrust := &openrtb_ext.ExtUserDigiTrust{ ID: "anyDigitrustID", KeyV: 1, diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bcb13724519..bd50fca9149 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -315,7 +315,12 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { } if err := ccpaPolicy.Validate(); err != nil { - errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("CCPA value is invalid and will be ignored. (%s)", err.Error())}) + errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) + + ccpaPolicy.Value = "" + if err := ccpaPolicy.Write(req); err != nil { + errL = append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) + } } impIDs := make(map[string]int, len(req.Imp)) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index fdd6b3a47cf..c3b9267bf8b 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -915,7 +915,7 @@ func TestCurrencyTrunc(t *testing.T) { assert.ElementsMatch(t, errL, []error{&expectedError}) } -func TestCCPAInvalidValueWarning(t *testing.T) { +func TestCCPAInvalid(t *testing.T) { deps := &endpointDeps{ &nobidExchange{}, newParamsValidator(t), @@ -943,21 +943,23 @@ func TestCCPAInvalidValueWarning(t *testing.T) { W: &ui, H: &ui, }, - Ext: json.RawMessage("{\"appnexus\": {\"placementId\": 5667}}"), + Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, Site: &openrtb.Site{ ID: "myID", }, Regs: &openrtb.Regs{ - Ext: json.RawMessage("{\"us_privacy\":\"invalid by length\"}"), + Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`), }, } errL := deps.validateRequest(&req) - expectedError := errortypes.Warning{Message: "CCPA value is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"} - assert.ElementsMatch(t, errL, []error{&expectedError}) + expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"} + assert.ElementsMatch(t, errL, []error{&expectedWarning}) + + assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } // nobidExchange is a well-behaved exchange which always bids "no bid". diff --git a/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json b/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json index 0a9fe656362..d62f40438b4 100644 --- a/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json +++ b/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json @@ -1,68 +1,69 @@ { - "description": "Video endpoint valid request.", + "description": "Video endpoint valid request due to missing pods.", - "requestPayload": -{ - "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", - "accountid": "555888777", - - "site": { - "page": "prebid.com" - }, - "user": { - "buyeruids": { - "appnexus": "unique_id_an", - "rubicon": "unique_id_rubi" + "requestPayload": { + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] }, - "gdpr": { - "consentrequired": false, - "consentstring": "something" + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 }, - "yob": 1991, - "gender": "F", - "keywords": "Hotels, Travelling" - }, - "device11": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", - "ip": "123.145.167.10", - "devicetype": 1, - "dnt": 33, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "lmt": 44, - "os": "mac os", - "w": 640, - "h": 480, - "didsha1": "didsha1", - "didmd5": "didmd5", - "dpidsha1": "dpidsha1", - "dpidmd5": "dpidmd5", - "macsha1": "macsha1", - "macmd5": "macmd5" - }, - "includebrandcategory":{ - "primaryadserver": 1, - "publisher": "" - }, - "video": { - "w": 640, - "h": 480, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2,3,5,6 - ] - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "livestream": 0 - }, - "cacheconfig": { - "ttl": 42 + "cacheconfig": { + "ttl": 42 + } } -} } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample.json index caa16f523dc..7ccdbf83a46 100644 --- a/endpoints/openrtb2/sample-requests/video/video_valid_sample.json +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample.json @@ -1,85 +1,86 @@ { "description": "Video endpoint valid request.", - "requestPayload": -{ - "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", - "accountid": "555888777", - "podconfig": { - "durationrangesec": [ - 30 - ], - "requireexactduration": true, - "pods": [ - { - "podid": 1, - "adpoddurationsec": 180, - "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" - }, - { - "podid": 2, - "adpoddurationsec": 150, - "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + "requestPayload": { + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } } - ] - }, - "site": { - "page": "prebid.com" - }, - "user": { - "buyeruids": { - "appnexus": "unique_id_an", - "rubicon": "unique_id_rubi" }, - "gdpr": { - "consentrequired": false, - "consentstring": "something" + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 }, - "yob": 1991, - "gender": "F", - "keywords": "Hotels, Travelling" - }, - "device11": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", - "ip": "123.145.167.10", - "devicetype": 1, - "dnt": 33, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "lmt": 44, - "os": "mac os", - "w": 640, - "h": 480, - "didsha1": "didsha1", - "didmd5": "didmd5", - "dpidsha1": "dpidsha1", - "dpidmd5": "dpidmd5", - "macsha1": "macsha1", - "macmd5": "macmd5" - }, - "includebrandcategory":{ - "primaryadserver": 1, - "publisher": "" - }, - "video": { - "w": 640, - "h": 480, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2,3,5,6 - ] - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "livestream": 0 - }, - "cacheconfig": { - "ttl": 42 + "cacheconfig": { + "ttl": 42 + } } -} } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json new file mode 100644 index 00000000000..b512c68346e --- /dev/null +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json @@ -0,0 +1,88 @@ +{ + "description": "Video endpoint valid request.", + + "requestPayload": { + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "${malformed}" + } + }, + "user": { + "buyeruid": "anyId", + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 + }, + "cacheconfig": { + "ttl": 42 + } + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json new file mode 100644 index 00000000000..cfa389d4ce2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json @@ -0,0 +1,88 @@ +{ + "description": "Video endpoint valid request.", + + "requestPayload": { + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "1NYN" + } + }, + "user": { + "buyeruid": "anyId", + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 + }, + "cacheconfig": { + "ttl": 42 + } + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json index 504af2d61cd..c3ad776960a 100644 --- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json @@ -1,86 +1,87 @@ { - "description": "Video endpoint valid request.", + "description": "Video endpoint valid request with different durations.", - "requestPayload": -{ - "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", - "accountid": "555888777", - "podconfig": { - "durationrangesec": [ - 15, - 30 - ], - "requireexactduration": true, - "pods": [ - { - "podid": 1, - "adpoddurationsec": 180, - "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" - }, - { - "podid": 2, - "adpoddurationsec": 150, - "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + "requestPayload": { + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "podconfig": { + "durationrangesec": [ + 15, + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } } - ] - }, - "site": { - "page": "prebid.com" - }, - "user": { - "buyeruids": { - "appnexus": "unique_id_an", - "rubicon": "unique_id_rubi" }, - "gdpr": { - "consentrequired": false, - "consentstring": "something" + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 }, - "yob": 1991, - "gender": "F", - "keywords": "Hotels, Travelling" - }, - "device11": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", - "ip": "123.145.167.10", - "devicetype": 1, - "dnt": 33, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "lmt": 44, - "os": "mac os", - "w": 640, - "h": 480, - "didsha1": "didsha1", - "didmd5": "didmd5", - "dpidsha1": "dpidsha1", - "dpidmd5": "dpidmd5", - "macsha1": "macsha1", - "macmd5": "macmd5" - }, - "includebrandcategory":{ - "primaryadserver": 1, - "publisher": "" - }, - "video": { - "w": 640, - "h": 480, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2,3,5,6 - ] - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "livestream": 0 - }, - "cacheconfig": { - "ttl": 42 + "cacheconfig": { + "ttl": 42 + } } -} } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json index 68c3f4e1c15..6a9dc605ea2 100644 --- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json @@ -1,80 +1,85 @@ - { - "accountid": "555888777", - "podconfig": { - "durationrangesec": [ - 30 - ], - "requireexactduration": true, - "pods": [ - { - "podid": 1, - "adpoddurationsec": 180, - "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" - }, - { - "podid": 2, - "adpoddurationsec": 150, - "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + "description": "Video endpoint valid request with device data.", + + "requestPayload": { + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 } - ] - }, - "site": { - "page": "prebid.com" - }, - "user": { - "buyeruids": { - "appnexus": "unique_id_an", - "rubicon": "unique_id_rubi" }, - "gdpr": { - "consentrequired": false, - "consentstring": "something" + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "device": { + "ua": "TestHeaderSample", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 }, - "yob": 1991, - "gender": "F", - "keywords": "Hotels, Travelling" - }, - "device": { - "ua": "TestHeaderSample", - "ip": "123.145.167.10", - "devicetype": 1, - "dnt": 33, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "lmt": 44, - "os": "mac os", - "w": 640, - "h": 480, - "didsha1": "didsha1", - "didmd5": "didmd5", - "dpidsha1": "dpidsha1", - "dpidmd5": "dpidmd5", - "macsha1": "macsha1", - "macmd5": "macmd5" - }, - "includebrandcategory":{ - "primaryadserver": 1, - "publisher": "" - }, - "video": { - "w": 640, - "h": 480, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2,3,5,6 - ] - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "livestream": 0 - }, - "cacheconfig": { - "ttl": 42 + "cacheconfig": { + "ttl": 42 + } } -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json index e040a5625ba..199391865b2 100644 --- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json +++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json @@ -1,63 +1,69 @@ - { - "accountid": "555888777", - "podconfig": { - "durationrangesec": [ - 30 - ], - "requireexactduration": true, - "pods": [ - { - "podid": 1, - "adpoddurationsec": 180, - "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" - }, - { - "podid": 2, - "adpoddurationsec": 150, - "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + "description": "Video endpoint valid request without device data.", + + "requestPayload": { + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 } - ] - }, - "site": { - "page": "prebid.com" - }, - "user": { - "buyeruids": { - "appnexus": "unique_id_an", - "rubicon": "unique_id_rubi" }, - "gdpr": { - "consentrequired": false, - "consentstring": "something" + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 }, - "yob": 1991, - "gender": "F", - "keywords": "Hotels, Travelling" - }, - "includebrandcategory":{ - "primaryadserver": 1, - "publisher": "" - }, - "video": { - "w": 640, - "h": 480, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2,3,5,6 - ] - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "livestream": 0 - }, - "cacheconfig": { - "ttl": 42 + "cacheconfig": { + "ttl": 42 + } } -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 020a5196333..64c99fa5a3e 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -204,7 +204,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re deps.setFieldsImplicitly(r, bidReq) // move after merge errL = deps.validateRequest(bidReq) - if len(errL) > 0 { + if errortypes.ContainsFatalError(errL) { handleError(&labels, w, errL, &vo, &debugLog) return } @@ -232,7 +232,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { - errL = append(errL, acctIdErr) + errL := []error{err} handleError(&labels, w, errL, &vo, &debugLog) return } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 5ba34068f7b..631cb277f7f 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1,6 +1,7 @@ package openrtb2 import ( + "bytes" "context" "encoding/json" "errors" @@ -860,12 +861,12 @@ func TestParseVideoRequestWithUserAgentAndHeader(t *testing.T) { if err != nil { t.Fatalf("Failed to fetch a valid request: %v", err) } - headers := http.Header{} headers.Add("User-Agent", "TestHeader") deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -883,7 +884,8 @@ func TestParseVideoRequestWithUserAgentAndEmptyHeader(t *testing.T) { headers := http.Header{} deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -902,7 +904,8 @@ func TestParseVideoRequestWithoutUserAgentWithHeader(t *testing.T) { headers.Add("User-Agent", "TestHeader") deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, "TestHeader", req.Device.UA, "Device.ua should be taken from request header") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -920,7 +923,8 @@ func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) { headers := http.Header{} deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, "", req.Device.UA, "Device.ua should be empty") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -942,7 +946,8 @@ func TestParseVideoRequestWithEncodedUserAgentInHeader(t *testing.T) { headers.Add("User-Agent", uaEncoded) deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -963,7 +968,8 @@ func TestParseVideoRequestWithDecodedUserAgentInHeader(t *testing.T) { headers.Add("User-Agent", uaDecoded) deps := mockDeps(t, ex) - req, valErr, podErr := deps.parseVideoRequest(reqData, headers) + reqBody := string(getRequestPayload(t, reqData)) + req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers) assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header") assert.Equal(t, []error(nil), valErr, "No validation errors should be returned") @@ -1036,6 +1042,71 @@ func TestCreateImpressionTemplate(t *testing.T) { assert.Equal(t, res.Video.PlaybackMethod, []openrtb.PlaybackMethod{7, 8}, "Incorrect video playback method") } +func TestCCPA(t *testing.T) { + testCases := []struct { + description string + testFilePath string + expectConsentString bool + }{ + { + description: "Missing Consent", + testFilePath: "sample-requests/video/video_valid_sample.json", + expectConsentString: false, + }, + { + description: "Valid Consent", + testFilePath: "sample-requests/video/video_valid_sample_ccpa_valid.json", + expectConsentString: true, + }, + { + description: "Malformed Consent", + testFilePath: "sample-requests/video/video_valid_sample_ccpa_malformed.json", + expectConsentString: false, + }, + } + + for _, test := range testCases { + // Load Test Request + requestContainerBytes, err := ioutil.ReadFile(test.testFilePath) + if err != nil { + t.Fatalf("%s: Failed to fetch a valid request: %v", test.description, err) + } + requestBytes := getRequestPayload(t, requestContainerBytes) + + // Create HTTP Request + Response Recorder + httpRequest := httptest.NewRequest("POST", "/openrtb2/video", bytes.NewReader(requestBytes)) + httpResponseRecorder := httptest.NewRecorder() + + // Run Test + ex := &mockExchangeVideo{} + mockDeps(t, ex).VideoAuctionEndpoint(httpResponseRecorder, httpRequest, nil) + + // Validate Request To Exchange + // - An error should never be generated for CCPA problems. + if ex.lastRequest == nil { + t.Fatalf("%s: The request never made it into the exchange.", test.description) + } + extRegs := &openrtb_ext.ExtRegs{} + if err = json.Unmarshal(ex.lastRequest.Regs.Ext, extRegs); err != nil { + t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err) + } + if test.expectConsentString { + assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent") + } else { + assert.Empty(t, extRegs.USPrivacy, test.description+":consent") + } + + // Validate HTTP Response + responseBytes := httpResponseRecorder.Body.Bytes() + response := &openrtb_ext.BidResponseVideo{} + if err := json.Unmarshal(responseBytes, response); err != nil { + t.Fatalf("%s: Unable to unmarshal response.", test.description) + } + assert.Len(t, ex.lastRequest.Imp, 11, test.description+":imps") + assert.Len(t, response.AdPods, 5, test.description+":adpods") + } +} + func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} @@ -1166,3 +1237,19 @@ var testVideoStoredImpData = map[string]json.RawMessage{ var testVideoStoredRequestData = map[string]json.RawMessage{ "80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`), } + +func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo { + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + reqBody := getRequestPayload(t, reqData) + + reqVideo := &openrtb_ext.BidRequestVideo{} + if err := json.Unmarshal(reqBody, reqVideo); err != nil { + t.Fatalf("Failed to unmarshal the request: %v", err) + } + + return reqVideo +} diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 64579f2a2f6..11ac434595a 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -14,7 +14,7 @@ type Policy struct { Value string } -// ReadPolicy extracts the CCPA regulation policy from an OpenRTB regs ext. +// ReadPolicy extracts the CCPA regulation policy from an OpenRTB request. func ReadPolicy(req *openrtb.BidRequest) (Policy, error) { policy := Policy{} @@ -32,6 +32,10 @@ func ReadPolicy(req *openrtb.BidRequest) (Policy, error) { // Write mutates an OpenRTB bid request with the context of the CCPA policy. func (p Policy) Write(req *openrtb.BidRequest) error { if p.Value == "" { + return clearPolicy(req) + } + + if req == nil { return nil } @@ -59,6 +63,37 @@ func (p Policy) Write(req *openrtb.BidRequest) error { return err } +func clearPolicy(req *openrtb.BidRequest) error { + if req == nil { + return nil + } + + if req.Regs == nil { + return nil + } + + if len(req.Regs.Ext) == 0 { + return nil + } + + var extMap map[string]interface{} + err := json.Unmarshal(req.Regs.Ext, &extMap) + if err == nil { + delete(extMap, "us_privacy") + if len(extMap) == 0 { + req.Regs.Ext = nil + } else { + ext, err := json.Marshal(extMap) + if err == nil { + req.Regs.Ext = ext + } + return err + } + } + + return err +} + // Validate returns an error if the CCPA policy does not adhere to the IAB spec. func (p Policy) Validate() error { if err := ValidateConsent(p.Value); err != nil { diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 740f95a8a6a..e9b4c4525b1 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -112,6 +112,48 @@ func TestWrite(t *testing.T) { request: &openrtb.BidRequest{}, expected: &openrtb.BidRequest{}, }, + { + description: "Disabled - Nil Request", + policy: Policy{Value: ""}, + request: nil, + expected: nil, + }, + { + description: "Disabled - Empty Regs.Ext", + policy: Policy{Value: ""}, + request: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, + expected: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, + }, + { + description: "Disabled - Remove From Request", + policy: Policy{Value: ""}, + request: &openrtb.BidRequest{Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}}, + expected: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, + }, + { + description: "Disabled - Remove From Request, Leave Other req Values", + policy: Policy{Value: ""}, + request: &openrtb.BidRequest{Regs: &openrtb.Regs{ + COPPA: 42, + Ext: json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}}, + expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ + COPPA: 42}}, + }, + { + description: "Disabled - Remove From Request, Leave Other req.ext Values", + policy: Policy{Value: ""}, + request: &openrtb.BidRequest{Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"existing":"any","us_privacy":"toBeRemoved"}`)}}, + expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"existing":"any"}`)}}, + }, + { + description: "Enabled - Nil Request", + policy: Policy{Value: "anyValue"}, + request: nil, + expected: nil, + }, { description: "Enabled With Nil Request Regs Object", policy: Policy{Value: "anyValue"}, From 47bed2a1a2ab043113391c2ebe25338ecbd83446 Mon Sep 17 00:00:00 2001 From: Artur Aleksanyan Date: Tue, 9 Jun 2020 18:48:04 +0400 Subject: [PATCH 108/603] Add Pubnative bidder documentation (#1340) --- docs/bidders/pubnative.md | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/bidders/pubnative.md diff --git a/docs/bidders/pubnative.md b/docs/bidders/pubnative.md new file mode 100644 index 00000000000..a25cafe0cd5 --- /dev/null +++ b/docs/bidders/pubnative.md @@ -0,0 +1,62 @@ +# Pubnative Bidder + +## Prerequisite +Before adding PubNative as a new bidder, there are 3 prerequisites: +- As a Publisher, you need to have Prebid Mobile SDK integrated. +- You need a configured Prebid Server (either self-hosted or hosted by 3rd party). +- You need to be integrated with Ad Server SDK (e.g. Mopub) or internal product which communicates with Prebid Mobile SDK. + +Please see [documentation](https://developers.pubnative.net/docs/prebid-adding-pubnative-as-a-bidder) for more info. + +## Configuration + +- bidder should be always set to "pubnative" (`imp.ext.pubnative`) +- zone_id (int) should be always set to 1, unless special use case agreed with our account manager. (`imp.ext.pubnative.zone_id`) +- app_auth_token (string) is unique per publisher app. Please contact our account manager to obtain yours. (`imp.ext.pubnative.app_auth_token`) + +An example is illustrated in a section below. + +## Testing + +Please consult with our Account Manager for testing. +We need to confirm that your ad request is correctly received by our system. + +The following test parameters can be used to verify that Prebid Server is working properly with the +Pubnative adapter. + +The following json can be used to do a request to prebid server for verifying its integration with Pubnative adapter. + +```json +{ + "id": "some-impression-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubnative": { + "zone_id": 1, + "app_auth_token": "b620e282f3c74787beedda34336a4821" + } + } + } + ], + "device": { + "os": "android", + "h": 700, + "w": 375 + }, + "tmax": 500, + "test": 1 +} +``` \ No newline at end of file From c628f1a83238d9e0ec5b430dd196d597145e1d11 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Tue, 9 Jun 2020 11:15:01 -0400 Subject: [PATCH 109/603] Timeout notification monitoring and debugging (#1322) --- config/config.go | 31 ++++++++++++++++++++ config/config_test.go | 8 ++++++ config/util/loggers.go | 24 ++++++++++++++++ config/util/loggers_test.go | 32 +++++++++++++++++++++ docs/developers/add-new-bidder.md | 10 +++++++ exchange/adapter_map.go | 6 ++-- exchange/adapter_map_test.go | 5 ++-- exchange/bidder.go | 36 +++++++++++++++++++----- exchange/bidder_test.go | 18 ++++++------ exchange/exchange.go | 2 +- exchange/targeting_test.go | 4 ++- pbsmetrics/config/metrics.go | 11 ++++++++ pbsmetrics/go_metrics.go | 18 ++++++++++++ pbsmetrics/go_metrics_test.go | 3 ++ pbsmetrics/metrics.go | 1 + pbsmetrics/metrics_mock.go | 5 ++++ pbsmetrics/prometheus/prometheus.go | 23 +++++++++++++++ pbsmetrics/prometheus/prometheus_test.go | 21 ++++++++++++++ 18 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 config/util/loggers.go create mode 100644 config/util/loggers_test.go diff --git a/config/config.go b/config/config.go index 4e54bc712a2..559fc5dac19 100755 --- a/config/config.go +++ b/config/config.go @@ -66,6 +66,8 @@ type Configuration struct { PemCertsFile string `mapstructure:"certificates_file"` // Custom headers to handle request timeouts from queueing infrastructure RequestTimeoutHeaders RequestTimeoutHeaders `mapstructure:"request_timeout_headers"` + // Debug/logging flags go here + Debug Debug `mapstructure:"debug"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -104,6 +106,7 @@ func (cfg *Configuration) validate() configErrors { errs = cfg.GDPR.validate(errs) errs = cfg.CurrencyConverter.validate(errs) errs = validateAdapters(cfg.Adapters, errs) + errs = cfg.Debug.validate(errs) return errs } @@ -450,6 +453,30 @@ type DefReqFiles struct { FileName string `mapstructure:"name"` } +type Debug struct { + TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"` +} + +func (cfg *Debug) validate(errs configErrors) configErrors { + return cfg.TimeoutNotification.validate(errs) +} + +type TimeoutNotification struct { + // Log timeout notifications in the application log + Log bool `mapstructure:"log"` + // Fraction of notifications to log + SamplingRate float32 `mapstructure:"sampling_rate"` + // Only log failures + FailOnly bool `mapstructure:"fail_only"` +} + +func (cfg *TimeoutNotification) validate(errs configErrors) configErrors { + if cfg.SamplingRate < 0.0 || cfg.SamplingRate > 1.0 { + errs = append(errs, fmt.Errorf("debug.timeout_notification.sampling_rate must be positive and not greater than 1.0. Got %f", cfg.SamplingRate)) + } + return errs +} + // New uses viper to get our server configurations. func New(v *viper.Viper) (*Configuration, error) { var c Configuration @@ -820,6 +847,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") + v.SetDefault("debug.timeout_notification.log", false) + v.SetDefault("debug.timeout_notification.sampling_rate", 0.0) + v.SetDefault("debug.timeout_notification.fail_only", false) + // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetEnvPrefix("PBS") diff --git a/config/config_test.go b/config/config_test.go index 92794d7941e..ee8e68e7025 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -426,6 +426,14 @@ func TestCookieSizeError(t *testing.T) { } } +func TestValidateDebug(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.Debug.TimeoutNotification.SamplingRate = 1.1 + + err := cfg.validate() + assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed") +} + func newDefaultConfig(t *testing.T) *Configuration { v := viper.New() SetupViper(v, "") diff --git a/config/util/loggers.go b/config/util/loggers.go new file mode 100644 index 00000000000..88702e68763 --- /dev/null +++ b/config/util/loggers.go @@ -0,0 +1,24 @@ +package util + +import ( + "math/rand" +) + +type logMsg func(string, ...interface{}) + +type randomGenerator func() float32 + +// LogRandomSample will log a randam sample of the messages it is sent, based on the chance to log +// chance = 1.0 => always log, +// chance = 0.0 => never log +func LogRandomSample(msg string, logger logMsg, chance float32) { + logRandomSampleImpl(msg, logger, chance, rand.Float32) +} + +func logRandomSampleImpl(msg string, logger logMsg, chance float32, randGenerator randomGenerator) { + if chance < 1.0 && randGenerator() > chance { + // this is the chance we don't log anything + return + } + logger(msg) +} diff --git a/config/util/loggers_test.go b/config/util/loggers_test.go new file mode 100644 index 00000000000..4bfab967ec4 --- /dev/null +++ b/config/util/loggers_test.go @@ -0,0 +1,32 @@ +package util + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLogRandomSample(t *testing.T) { + + const expected string = `This is test line 2 +This is test line 3 +` + + myRand := rand.New(rand.NewSource(1337)) + var buf bytes.Buffer + + mylogger := func(msg string, args ...interface{}) { + buf.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...)) + } + + logRandomSampleImpl("This is test line 1", mylogger, 0.5, myRand.Float32) + logRandomSampleImpl("This is test line 2", mylogger, 0.5, myRand.Float32) + logRandomSampleImpl("This is test line 3", mylogger, 0.5, myRand.Float32) + logRandomSampleImpl("This is test line 4", mylogger, 0.5, myRand.Float32) + logRandomSampleImpl("This is test line 5", mylogger, 0.5, myRand.Float32) + + assert.EqualValues(t, expected, buf.String()) +} diff --git a/docs/developers/add-new-bidder.md b/docs/developers/add-new-bidder.md index e68185fdd1c..d76a1fd2fbf 100644 --- a/docs/developers/add-new-bidder.md +++ b/docs/developers/add-new-bidder.md @@ -46,6 +46,16 @@ If bidder is going to support long form video make sure bidder has: Note: `bid.bidVideo.PrimaryCategory` or `TypedBid.bid.Cat` should be specified. To learn more about IAB categories, please refer to this convenience link (not the final official definition): [IAB categories](https://adtagmacros.com/list-of-iab-categories-for-advertisement/) +### Timeout notification support +This is an optional feature. If you wish to get timeout notifications when a bid request from PBS times out, you can implement the +`MakeTimeoutNotification` method in your adapter. If you do not wish timeout notification, do not implement the method. + +`func (a *Adapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error)` + +Here the `RequestData` supplied as an argument is the request returned from `MakeRequests` that timed out. If an adapter generates +multiple requests, and more than one of them times out, then there will be a call to `MakeTimeoutNotification` for each failed +request. The function should then return a `RequestData` object that will be the timeout notification to be sent to the bidder, or a list of errors encountered trying to create the timeout notification request. Timeout notifications will not generate subsequent timeout notifications if they timeout or fail. + ## Test Your Bidder ### Automated Tests diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 390016117fb..c8fbb775a21 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -5,6 +5,8 @@ import ( "net/http" "strings" + "github.com/prebid/prebid-server/pbsmetrics" + "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/adform" @@ -85,7 +87,7 @@ import ( // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter // to register itself. No wading through Exchange code to find it. -func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapters.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder { +func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapters.BidderInfos, me pbsmetrics.MetricsEngine) map[openrtb_ext.BidderName]adaptedBidder { ortbBidders := map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.Bidder33Across: ttx.New33AcrossBidder(cfg.Adapters[string(openrtb_ext.Bidder33Across)].Endpoint), openrtb_ext.BidderAdform: adform.NewAdformBidder(client, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), @@ -190,7 +192,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter for name, bidder := range ortbBidders { // Clean out any disabled bidders if infos[string(name)].Status == adapters.StatusActive { - allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client) + allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me) } } diff --git a/exchange/adapter_map_test.go b/exchange/adapter_map_test.go index a732f357897..f472ab1d988 100644 --- a/exchange/adapter_map_test.go +++ b/exchange/adapter_map_test.go @@ -7,11 +7,12 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config" ) func TestNewAdapterMap(t *testing.T) { cfg := &config.Configuration{Adapters: blankAdapterConfig(openrtb_ext.BidderList())} - adapterMap := newAdapterMap(nil, cfg, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList())) + adapterMap := newAdapterMap(nil, cfg, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), &metricsConfig.DummyMetricsEngine{}) for _, bidderName := range openrtb_ext.BidderMap { if bidder, ok := adapterMap[bidderName]; bidder == nil || !ok { t.Errorf("adapterMap missing expected Bidder: %s", string(bidderName)) @@ -38,7 +39,7 @@ func TestNewAdapterMapDisabledAdapters(t *testing.T) { } } } - adapterMap := newAdapterMap(nil, &config.Configuration{Adapters: cfgAdapters}, adapters.ParseBidderInfos(cfgAdapters, "../static/bidder-info", bidderList)) + adapterMap := newAdapterMap(nil, &config.Configuration{Adapters: cfgAdapters}, adapters.ParseBidderInfos(cfgAdapters, "../static/bidder-info", bidderList), &metricsConfig.DummyMetricsEngine{}) for _, bidderName := range openrtb_ext.BidderMap { if bidder, ok := adapterMap[bidderName]; bidder == nil || !ok { if inList(bidderList, bidderName) { diff --git a/exchange/bidder.go b/exchange/bidder.go index 7a53db5ee97..f9b4a522343 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -10,13 +10,18 @@ import ( "net/http" "time" + "github.com/golang/glog" + "github.com/prebid/prebid-server/config/util" + "github.com/mxmCherry/openrtb" nativeRequests "github.com/mxmCherry/openrtb/native/request" nativeResponse "github.com/mxmCherry/openrtb/native/response" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbsmetrics" "golang.org/x/net/context/ctxhttp" ) @@ -82,16 +87,20 @@ type pbsOrtbSeatBid struct { // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) -func adaptBidder(bidder adapters.Bidder, client *http.Client) adaptedBidder { +func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine) adaptedBidder { return &bidderAdapter{ - Bidder: bidder, - Client: client, + Bidder: bidder, + Client: client, + DebugConfig: cfg.Debug, + me: me, } } type bidderAdapter struct { - Bidder adapters.Bidder - Client *http.Client + Bidder adapters.Bidder + Client *http.Client + DebugConfig config.Debug + me pbsmetrics.MetricsEngine } func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currencies.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { @@ -365,8 +374,21 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou httpReq, err := http.NewRequest(toReq.Method, toReq.Uri, bytes.NewBuffer(toReq.Body)) if err == nil { httpReq.Header = req.Headers - ctxhttp.Do(ctx, bidder.Client, httpReq) - // No validation yet on sending notifications + httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq) + success := (err == nil && httpResp.StatusCode >= 200 && httpResp.StatusCode < 300) + bidder.me.RecordTimeoutNotice(success) + if bidder.DebugConfig.TimeoutNotification.Log && !(bidder.DebugConfig.TimeoutNotification.FailOnly && success) { + var msg string + if err == nil { + msg = fmt.Sprintf("TimeoutNotification: status:(%d) body:%s", httpResp.StatusCode, string(toReq.Body)) + } else { + msg = fmt.Sprintf("TimeoutNotification: error:(%s) body:%s", err.Error(), string(toReq.Body)) + } + // If logging is turned on, and logging is not disallowed via FailOnly + util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) + } + } else { + bidder.me.RecordTimeoutNotice(false) } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index f20b431c13a..fa04e6a4771 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -12,8 +12,10 @@ import ( "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/openrtb_ext" + metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config" "github.com/stretchr/testify/assert" nativeRequests "github.com/mxmCherry/openrtb/native/request" @@ -64,7 +66,7 @@ func TestSingleBidder(t *testing.T) { }, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) @@ -152,7 +154,7 @@ func TestMultiBidder(t *testing.T) { }}, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) @@ -510,7 +512,7 @@ func TestMultiCurrencies(t *testing.T) { ) // Execute: - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -658,7 +660,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid( context.Background(), @@ -824,7 +826,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -939,7 +941,7 @@ func TestServerCallDebugging(t *testing.T) { Headers: http.Header{}, }, } - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() bids, _ := bidder.requestBid( @@ -1051,7 +1053,7 @@ func TestMobileNativeTypes(t *testing.T) { }, bidResponse: tc.mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client()) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() seatBids, _ := bidder.requestBid( @@ -1072,7 +1074,7 @@ func TestMobileNativeTypes(t *testing.T) { } func TestErrorReporting(t *testing.T) { - bidder := adaptBidder(&bidRejector{}, nil) + bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) currencyConverter := currencies.NewRateConverterDefault() bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if bids != nil { diff --git a/exchange/exchange.go b/exchange/exchange.go index 6d51b87de4a..660beb641ef 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -69,7 +69,7 @@ type bidResponseWrapper struct { func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter) Exchange { e := new(exchange) - e.adapterMap = newAdapterMap(client, cfg, infos) + e.adapterMap = newAdapterMap(client, cfg, infos, metricsEngine) e.cache = cache e.cacheTime = time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond e.me = metricsEngine diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index f86309684c6..72de1d4261f 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,12 +8,14 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/pbsmetrics" metricsConf "github.com/prebid/prebid-server/pbsmetrics/config" + metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" @@ -132,7 +134,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ mockServerURL: mockServerURL, bids: bids, - }, client) + }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) } return adapterMap } diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index e1cdaceb0e5..4e249785ba6 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -188,6 +188,13 @@ func (me *MultiMetricsEngine) RecordRequestQueueTime(success bool, requestType p } } +// RecordTimeoutNotice across all engines +func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) { + for _, thisME := range *me { + thisME.RecordTimeoutNotice(success) + } +} + // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) type DummyMetricsEngine struct{} @@ -262,3 +269,7 @@ func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length // RecordRequestQueueTime as a noop func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) { } + +// RecordTimeoutNotice as a noop +func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { +} diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index ff3d9681fb1..1ced4d57269 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -48,6 +48,9 @@ type Metrics struct { ImpsTypeAudio metrics.Meter ImpsTypeNative metrics.Meter + TimeoutNotificationSuccess metrics.Meter + TimeoutNotificationFailure metrics.Meter + AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics @@ -131,6 +134,9 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ImpsTypeAudio: blankMeter, ImpsTypeNative: blankMeter, + TimeoutNotificationSuccess: blankMeter, + TimeoutNotificationFailure: blankMeter, + AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), MetricsDisabled: disableMetrics, @@ -209,6 +215,9 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry) newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry) + + newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry) + newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry) return newMetrics } @@ -544,6 +553,15 @@ func (me *Metrics) RecordRequestQueueTime(success bool, requestType RequestType, } +func (me *Metrics) RecordTimeoutNotice(success bool) { + if success { + me.TimeoutNotificationSuccess.Mark(1) + } else { + me.TimeoutNotificationFailure.Mark(1) + } + return +} + func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) { met, ok := meters[bidder] if ok { diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index 253ff69e3c2..25f75e77758 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -53,6 +53,9 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "queued_requests.video.rejected", m.RequestsQueueTimer[ReqTypeVideo][false]) ensureContains(t, registry, "queued_requests.video.accepted", m.RequestsQueueTimer[ReqTypeVideo][true]) + + ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess) + ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure) } func TestRecordBidType(t *testing.T) { diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index 611692c9c01..e65ba313338 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -275,4 +275,5 @@ type MetricsEngine interface { RecordStoredImpCacheResult(cacheResult CacheResult, inc int) RecordPrebidCacheRequestTime(success bool, length time.Duration) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) + RecordTimeoutNotice(sucess bool) } diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index 1f5b84b1e0f..482cbf24fae 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -101,3 +101,8 @@ func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length t func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) { me.Called(success, requestType, length) } + +// RecordTimeoutNotice mock +func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) { + me.Called(success) +} diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index d66defea4cd..e385b044981 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -28,6 +28,7 @@ type Metrics struct { requestsWithoutCookie *prometheus.CounterVec storedImpressionsCacheResult *prometheus.CounterVec storedRequestCacheResult *prometheus.CounterVec + timeout_notifications *prometheus.CounterVec // Adapter Metrics adapterBids *prometheus.CounterVec @@ -79,6 +80,11 @@ const ( requestRejectLabel = "requestRejectedLabel" ) +const ( + requestSuccessful = "ok" + requestFailed = "failed" +) + // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. func NewMetrics(cfg config.PrometheusMetrics) *Metrics { requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} @@ -147,6 +153,11 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of stored request cache requests attempts by hits or miss.", []string{cacheResultLabel}) + metrics.timeout_notifications = newCounter(cfg, metrics.Registry, + "timeout_notification", + "Count of timeout notifications triggered, and if they were successfully sent.", + []string{successLabel}) + metrics.adapterBids = newCounter(cfg, metrics.Registry, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -398,3 +409,15 @@ func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.Re requestStatusLabel: successLabelFormatted, }).Observe(length.Seconds()) } + +func (m *Metrics) RecordTimeoutNotice(success bool) { + if success { + m.timeout_notifications.With(prometheus.Labels{ + successLabel: requestSuccessful, + }).Inc() + } else { + m.timeout_notifications.With(prometheus.Labels{ + successLabel: requestFailed, + }).Inc() + } +} diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index e4d6a4f78d1..24c50492139 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -923,6 +923,27 @@ func TestRecordRequestQueueTimeMetric(t *testing.T) { } } +func TestTimeoutNotifications(t *testing.T) { + m := createMetricsForTesting() + + m.RecordTimeoutNotice(true) + m.RecordTimeoutNotice(true) + m.RecordTimeoutNotice(false) + + assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeout_notifications, + float64(2), + prometheus.Labels{ + successLabel: requestSuccessful, + }) + + assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeout_notifications, + float64(1), + prometheus.Labels{ + successLabel: requestFailed, + }) + +} + func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { m := dto.Metric{} counter.Write(&m) From 4361bf64f83a085227ac97781b43f0f30e60a053 Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 9 Jun 2020 19:32:38 +0300 Subject: [PATCH 110/603] Add Adtarget server adapter (#1319) * Add Adtarget server adapter * Suggested changes for Adtarget --- adapters/adtarget/adtarget.go | 189 ++++++++++++++++++ adapters/adtarget/adtarget_test.go | 11 + .../exemplary/media-type-mapping.json | 88 ++++++++ .../adtargettest/exemplary/simple-banner.json | 62 ++++++ .../adtargettest/exemplary/simple-video.json | 55 +++++ .../adtargettest/params/race/banner.json | 3 + .../adtargettest/params/race/video.json | 3 + .../adtargettest/supplemental/audio.json | 25 +++ .../supplemental/explicit-dimensions.json | 58 ++++++ .../adtargettest/supplemental/native.json | 25 +++ .../supplemental/wrong-impression-ext.json | 26 +++ .../wrong-impression-mapping.json | 77 +++++++ adapters/adtarget/params_test.go | 60 ++++++ adapters/adtarget/usersync.go | 12 ++ adapters/adtarget/usersync_test.go | 37 ++++ config/config.go | 2 + docs/bidders/adtarget.md | 5 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adtarget.go | 9 + static/bidder-info/adtarget.yaml | 11 + static/bidder-params/adtarget.json | 26 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 24 files changed, 791 insertions(+) create mode 100644 adapters/adtarget/adtarget.go create mode 100644 adapters/adtarget/adtarget_test.go create mode 100644 adapters/adtarget/adtargettest/exemplary/media-type-mapping.json create mode 100644 adapters/adtarget/adtargettest/exemplary/simple-banner.json create mode 100644 adapters/adtarget/adtargettest/exemplary/simple-video.json create mode 100644 adapters/adtarget/adtargettest/params/race/banner.json create mode 100644 adapters/adtarget/adtargettest/params/race/video.json create mode 100644 adapters/adtarget/adtargettest/supplemental/audio.json create mode 100644 adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json create mode 100644 adapters/adtarget/adtargettest/supplemental/native.json create mode 100644 adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json create mode 100644 adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json create mode 100644 adapters/adtarget/params_test.go create mode 100644 adapters/adtarget/usersync.go create mode 100644 adapters/adtarget/usersync_test.go create mode 100644 docs/bidders/adtarget.md create mode 100644 openrtb_ext/imp_adtarget.go create mode 100644 static/bidder-info/adtarget.yaml create mode 100644 static/bidder-params/adtarget.json diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go new file mode 100644 index 00000000000..77622d458a4 --- /dev/null +++ b/adapters/adtarget/adtarget.go @@ -0,0 +1,189 @@ +package adtarget + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type AdtargetAdapter struct { + endpoint string +} + +type adtargetImpExt struct { + Adtarget openrtb_ext.ExtImpAdtarget `json:"adtarget"` +} + +func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + totalImps := len(request.Imp) + errors := make([]error, 0, totalImps) + imp2source := make(map[int][]int) + + for i := 0; i < totalImps; i++ { + + sourceId, err := validateImpressionAndSetExt(&request.Imp[i]) + + if err != nil { + errors = append(errors, err) + continue + } + + if _, ok := imp2source[sourceId]; !ok { + imp2source[sourceId] = make([]int, 0, totalImps-i) + } + + imp2source[sourceId] = append(imp2source[sourceId], i) + + } + + totalReqs := len(imp2source) + if 0 == totalReqs { + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + reqs := make([]*adapters.RequestData, 0, totalReqs) + + imps := request.Imp + request.Imp = make([]openrtb.Imp, 0, len(imps)) + for sourceId, impIndexes := range imp2source { + request.Imp = request.Imp[:0] + + for i := 0; i < len(impIndexes); i++ { + request.Imp = append(request.Imp, imps[impIndexes[i]]) + } + + body, err := json.Marshal(request) + if err != nil { + errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err)) + return nil, errors + } + + reqs = append(reqs, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint + fmt.Sprintf("?aid=%d", sourceId), + Body: body, + Headers: headers, + }) + } + + return reqs, errors +} + +func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if httpRes.StatusCode == http.StatusNoContent { + return nil, nil + } + if httpRes.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", httpRes.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("error while decoding response, err: %s", err), + }} + } + + bidResponse := adapters.NewBidderResponse() + var errors []error + + var impOK bool + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + + bid := sb.Bid[i] + + impOK = false + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range bidReq.Imp { + if imp.ID == bid.ImpID { + + impOK = true + + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + break + } + } + } + + if !impOK { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("ignoring bid id=%s, request doesn't contain any impression with id=%s", bid.ID, bid.ImpID), + }) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: mediaType, + }) + } + } + + return bidResponse, errors +} + +func validateImpressionAndSetExt(imp *openrtb.Imp) (int, error) { + + if imp.Banner == nil && imp.Video == nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, Adtarget supports only Video and Banner", imp.ID), + } + } + + if 0 == len(imp.Ext) { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, extImpBidder is empty", imp.ID), + } + } + + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpAdtarget{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + // common extension for all impressions + var impExtBuffer []byte + + impExtBuffer, err = json.Marshal(&adtargetImpExt{ + Adtarget: impExt, + }) + + if impExt.BidFloor > 0 { + imp.BidFloor = impExt.BidFloor + } + + imp.Ext = impExtBuffer + + return impExt.SourceId, nil +} + +func NewAdtargetBidder(endpoint string) *AdtargetAdapter { + return &AdtargetAdapter{ + endpoint: endpoint, + } +} diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go new file mode 100644 index 00000000000..93732988120 --- /dev/null +++ b/adapters/adtarget/adtarget_test.go @@ -0,0 +1,11 @@ +package adtarget + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "adtargettest", NewAdtargetBidder("http://ghb.console.adtarget.com.tr/pbs/ortb")) +} diff --git a/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json new file mode 100644 index 00000000000..518268d4fea --- /dev/null +++ b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "adtarget": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 3.5, + "w": 900, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adtarget/adtargettest/exemplary/simple-banner.json b/adapters/adtarget/adtargettest/exemplary/simple-banner.json new file mode 100644 index 00000000000..b63739bda0f --- /dev/null +++ b/adapters/adtarget/adtargettest/exemplary/simple-banner.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [ + {"w":300,"h":250}, + {"w":300,"h":600} + ] + }, + "bidfloor": 20, + "ext": { + "adtarget": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/adtarget/adtargettest/exemplary/simple-video.json b/adapters/adtarget/adtargettest/exemplary/simple-video.json new file mode 100644 index 00000000000..4dc4547d7d1 --- /dev/null +++ b/adapters/adtarget/adtargettest/exemplary/simple-video.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "adtarget": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/adtarget/adtargettest/params/race/banner.json b/adapters/adtarget/adtargettest/params/race/banner.json new file mode 100644 index 00000000000..1d6658c71ab --- /dev/null +++ b/adapters/adtarget/adtargettest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "aid": 350975 +} diff --git a/adapters/adtarget/adtargettest/params/race/video.json b/adapters/adtarget/adtargettest/params/race/video.json new file mode 100644 index 00000000000..fe4207ef05c --- /dev/null +++ b/adapters/adtarget/adtargettest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "aid": 331133 +} diff --git a/adapters/adtarget/adtargettest/supplemental/audio.json b/adapters/adtarget/adtargettest/supplemental/audio.json new file mode 100644 index 00000000000..e2148e9db99 --- /dev/null +++ b/adapters/adtarget/adtargettest/supplemental/audio.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-audio-request", + "imp": [ + { + "id": "unsupported-audio-imp", + "audio": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-audio-imp, Adtarget supports only Video and Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json new file mode 100644 index 00000000000..a4e487466ea --- /dev/null +++ b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "adtarget": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/adtarget/adtargettest/supplemental/native.json b/adapters/adtarget/adtargettest/supplemental/native.json new file mode 100644 index 00000000000..3d9aa6630eb --- /dev/null +++ b/adapters/adtarget/adtargettest/supplemental/native.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "native": { + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, Adtarget supports only Video and Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json new file mode 100644 index 00000000000..1986dfaf13f --- /dev/null +++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "video": { + "w": 100, + "h": 200 + }, + "ext": { + "bidder": { + "aid": "some string instead of int" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, error while decoding impExt, err: json: cannot unmarshal string into Go struct field ExtImpAdtarget.aid of type int", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..0dffdb2bebb --- /dev/null +++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "adtarget": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "SOME-WRONG-IMP-ID", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "ignoring bid id=test-bid-id, request doesn't contain any impression with id=SOME-WRONG-IMP-ID", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go new file mode 100644 index 00000000000..b128d11c9cf --- /dev/null +++ b/adapters/adtarget/params_test.go @@ -0,0 +1,60 @@ +package adtarget + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adtarget.json +// These also validate the format of the external API: request.imp[i].ext.adtarget +// TestValidParams makes sure that the adtarget schema accepts all imp.ext fields which we intend to support. + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdtarget, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adtarget params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adtarget schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdtarget, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"aid":123}`, + `{"aid":123,"placementId":1234}`, + `{"aid":123,"siteId":4321}`, + `{"aid":123,"siteId":0,"bidFloor":0}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"aid":"123"}`, + `{"aid":"0"}`, + `{"aid":"123","placementId":"123"}`, + `{"aid":123, "placementId":"123", "siteId":"321"}`, +} diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go new file mode 100644 index 00000000000..20bced25c72 --- /dev/null +++ b/adapters/adtarget/usersync.go @@ -0,0 +1,12 @@ +package adtarget + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adtarget", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go new file mode 100644 index 00000000000..3ab2ed5b5df --- /dev/null +++ b/adapters/adtarget/usersync_test.go @@ -0,0 +1,37 @@ +package adtarget + +import ( + "fmt" + "github.com/prebid/prebid-server/privacy/ccpa" + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdtargetSyncer(t *testing.T) { + syncURL := "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D" + fmt.Println("adtarget sync") + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdtargetSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "123", + }, + CCPA: ccpa.Policy{ + Value: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 559fc5dac19..07384f9d2d3 100755 --- a/config/config.go +++ b/config/config.go @@ -548,6 +548,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtarget, "https://sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") // openrtb_ext.BidderAdOcean doesn't have a good default. @@ -752,6 +753,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") + v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") diff --git a/docs/bidders/adtarget.md b/docs/bidders/adtarget.md new file mode 100644 index 00000000000..b658a728a2b --- /dev/null +++ b/docs/bidders/adtarget.md @@ -0,0 +1,5 @@ +# Adtarget bidder + +To use the Adtarget bidder you will need an aid from an exchange account on [https://console.adtarget.com.tr](adtarget.com.tr). + +For further information, please contact kamil@adtarget.com.tr \ No newline at end of file diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index c8fbb775a21..2ea8f7fb648 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adpone" + "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/aja" @@ -99,6 +100,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdOcean: adocean.NewAdOceanBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdOcean))].Endpoint), openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), openrtb_ext.BidderAdpone: adpone.NewAdponeBidder(cfg.Adapters[string(openrtb_ext.BidderAdpone)].Endpoint), + openrtb_ext.BidderAdtarget: adtarget.NewAdtargetBidder(cfg.Adapters[string(openrtb_ext.BidderAdtarget)].Endpoint), openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), openrtb_ext.BidderAdvangelists: advangelists.NewAdvangelistsBidder(cfg.Adapters[string(openrtb_ext.BidderAdvangelists)].Endpoint), openrtb_ext.BidderAJA: aja.NewAJABidder(cfg.Adapters[string(openrtb_ext.BidderAJA)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index b3ecddb06cd..659c6616fea 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -33,6 +33,7 @@ const ( BidderAdpone BidderName = "adpone" BidderAdmixer BidderName = "admixer" BidderAdOcean BidderName = "adocean" + BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderAJA BidderName = "aja" @@ -110,6 +111,7 @@ var BidderMap = map[string]BidderName{ "admixer": BidderAdmixer, "adocean": BidderAdOcean, "adpone": BidderAdpone, + "adtarget": BidderAdtarget, "adtelligent": BidderAdtelligent, "advangelists": BidderAdvangelists, "aja": BidderAJA, diff --git a/openrtb_ext/imp_adtarget.go b/openrtb_ext/imp_adtarget.go new file mode 100644 index 00000000000..a8ac70a17d1 --- /dev/null +++ b/openrtb_ext/imp_adtarget.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpAdtarget defines the contract for bidrequest.imp[i].ext.adtarget +type ExtImpAdtarget struct { + SourceId int `json:"aid"` + PlacementId int `json:"placementId,omitempty"` + SiteId int `json:"siteId,omitempty"` + BidFloor float64 `json:"bidFloor,omitempty"` +} diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml new file mode 100644 index 00000000000..d52f18ac697 --- /dev/null +++ b/static/bidder-info/adtarget.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "kamil@adtarget.com.tr" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/adtarget.json b/static/bidder-params/adtarget.json new file mode 100644 index 00000000000..195bf2dd430 --- /dev/null +++ b/static/bidder-params/adtarget.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adtarget Adapter Params", + "description": "A schema which validates params accepted by the Adtarget adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + }, + "siteId": { + "type": "integer", + "description": "An ID which identifies the site selling the impression" + }, + "aid": { + "type": "integer", + "description": "An ID which identifies the channel" + }, + "bidFloor": { + "type": "number", + "description": "BidFloor, US Dollars" + } + }, + "required": ["aid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 5dccf855add..751d2aabfbe 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -12,6 +12,7 @@ import ( "github.com/prebid/prebid-server/adapters/admixer" "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adpone" + "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/aja" @@ -84,6 +85,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdmixer, admixer.NewAdmixerSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdOcean, adocean.NewAdOceanSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdpone, adpone.NewadponeSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtarget, adtarget.NewAdtargetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index ddd067e8be7..c9ef382fc92 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -21,6 +21,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdmixer): syncConfig, string(openrtb_ext.BidderAdOcean): syncConfig, string(openrtb_ext.BidderAdpone): syncConfig, + string(openrtb_ext.BidderAdtarget): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, From 86fa52b19e92348d070d97fefd83d21974bbf25d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 9 Jun 2020 13:23:57 -0400 Subject: [PATCH 111/603] Update Auction OpenRTB Sample (#1342) * Update Auction OpenRTB Sample * Removed Extra "Or" --- docs/endpoints/openrtb2/auction.md | 209 +++++++++++++++++------------ 1 file changed, 126 insertions(+), 83 deletions(-) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index 67430e51481..d09216188b8 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -14,53 +14,94 @@ This endpoint runs an auction with the given OpenRTB 2.5 bid request. ### Sample request -The [Prebid sample ad](http://prebid.org/examples/pbjs_demo.html) can be loaded with the request sample [here](../../../endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json). +This is a sample OpenRTB 2.5 bid request for a Xandr (formerly AppNexus) test placement. Please note, the Xandr Ad Server will only +respond with a bid if the "test" field is set to 1. -Other examples can be found in [endpoints/openrtb2/sample-requests/valid-whole/exemplary](../../../endpoints/openrtb2/sample-requests/valid-whole/exemplary). +``` +{ + "id": "some-request-id", + "test": 1, + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "tmax": 500 +} +``` + +Additional examples can be found in [endpoints/openrtb2/sample-requests/valid-whole](../../../endpoints/openrtb2/sample-requests/valid-whole). ### Sample Response This endpoint will respond with either: -- An OpenRTB 2.5 BidResponse, or -- An HTTP 400 status code if the request is malformed +- An OpenRTB 2.5 bid response, or +- HTTP 400 if the request is malformed, or +- HTTP 503 if the account or app specified in the request is blacklisted -A "hello world" response from the prebid sample ad request is shown below. +This is the corresponding response to the above sample OpenRTB 2.5 bid request, with the `ext.debug` field removed and the `seatbid.bid.adm` field simplified. ``` { "id": "some-request-id", - "seatbid": [ - { - "seat": "appnexus" - "bid": [ - { - "id": "4625436751433509010", - "impid": "some-impression-id", - "price": 0.5, - "adm": "", - "adid": "29681110", - "adomain": [ - "appnexus.com" - ], - "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250, - "ext": { - "bidder": { - "appnexus": { - "brand_id": 1, - "auction_id": 6127490747252133000, - "bidder_id": 2 - } - } + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "145556724130495288", + "impid": "some-impression-id", + "price": 0.01, + "adm": "", + "adid": "107987536", + "adomain": [ + "appnexus.com" + ], + "iurl": "https://nym1-ib.adnxs.com/cr?id=107987536", + "cid": "3532", + "crid": "107987536", + "w": 600, + "h": 500, + "ext": { + "prebid": { + "type": "banner", + "video": { + "duration": 0, + "primary_category": "" + } + }, + "bidder": { + "appnexus": { + "brand_id": 1, + "auction_id": 7311907164510136364, + "bidder_id": 2, + "bid_ad_type": 0 } } - ] - } - ] + } + }] + }], + "cur": "USD", + "ext": { + "responsetimemillis": { + "appnexus": 10 + }, + "tmaxrequest": 500 + } } ``` @@ -69,12 +110,12 @@ A "hello world" response from the prebid sample ad request is shown below. #### Conventions OpenRTB 2.5 permits exchanges to define their own extensions to any object from the spec. -These fall under the `ext` property of JSON objects. +These fall under the `ext` field of JSON objects. If `ext` is defined on an object, Prebid Server uses the following conventions: -1. `ext` in "Request objects" uses `ext.prebid` and/or `ext.{anyBidderCode}`. -2. `ext` on "Response objects" uses `ext.prebid` and/or `ext.bidder`. +1. `ext` in "request objects" uses `ext.prebid` and/or `ext.{anyBidderCode}`. +2. `ext` on "response objects" uses `ext.prebid` and/or `ext.bidder`. The only exception here is the top-level `BidResponse`, because it's bidder-independent. `ext.{anyBidderCode}` and `ext.bidder` extensions are defined by bidders. @@ -84,9 +125,9 @@ Exceptions are made for extensions with "standard" recommendations: - `request.user.ext.digitrust` -- To support Digitrust - `request.regs.ext.gdpr` and `request.user.ext.consent` -- To support GDPR +- `request.regs.us_privacy` -- To support CCPA - `request.site.ext.amp` -- To identify AMP as the request source - `request.app.ext.source` and `request.app.ext.version` -- To support identifying the displaymanager/SDK in mobile apps. If given, we expect these to be strings. -- `request.regs.coppa` -- to support COPPA #### Bid Adjustments @@ -98,7 +139,7 @@ If you find that some bidders use Gross bids, publishers can adjust for it with "ext": { "prebid": { "bidadjustmentfactors": { - "appnexus: 0.8, + "appnexus": 0.8, "rubicon": 0.7 } } @@ -126,8 +167,8 @@ to set these params on the response at `response.seatbid[i].bid[j].ext.prebid.ta "pricegranularity": { "precision": 2, "ranges": [{ - "max":20.00, - "increment":0.10 // This is equivalent to the deprecated "pricegranularity": "medium" + "max": 20.00, + "increment": 0.10 // This is equivalent to the deprecated "pricegranularity": "medium" }] }, "includewinners": false, // Optional param defaulting to true @@ -146,23 +187,29 @@ One of "includewinners" or "includebidderkeys" must be true (both default to tru MediaType PriceGranularity (PBS-Java only) - when a single OpenRTB request contains multiple impressions with different mediatypes, or a single impression supports multiple formats, the different mediatypes may need different price granularities. If `mediatypepricegranularity` is present, `pricegranularity` would only be used for any mediatypes not specified. ``` - "ext": { - "prebid": { - "targeting": { - "mediatypepricegranularity": { - "banner": { "ranges": [ - {"max": 20, "increment": 0.5} - ]}, - "video": { "ranges": [ - {"max": 10, "increment": 1}, - {"max": 20, "increment": 2}, - {"max": 50, "increment": 5} - ]} - } - } - "includewinners": true - } - } +{ + "ext": { + "prebid": { + "targeting": { + "mediatypepricegranularity": { + "banner": { + "ranges": [ + {"max": 20, "increment": 0.5} + ] + }, + "video": { + "ranges": [ + {"max": 10, "increment": 1}, + {"max": 20, "increment": 2}, + {"max": 50, "increment": 5} + ] + } + } + }, + "includewinners": true + } + } +} ``` **Response format** (returned in `bid.ext.prebid.targeting`) @@ -238,22 +285,20 @@ This can be used to request bids from the same Bidder with different params. For ``` { - "imp": [ - { - "id": "some-impression-id", - "video": { - "mimes": ["video/mp4"] + "imp": [{ + "id": "some-impression-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 123 }, - "ext": { - "appnexus: { - "placementId": 123 - }, - "districtm": { - "placementId": 456 - } + "districtm": { + "placementId": 456 } } - ], + }], "ext": { "prebid": { "aliases": { @@ -303,12 +348,12 @@ For example, a request may return this in `response.ext` "ext": { "errors": { "appnexus": [{ - "code": 2, - "message": "A hybrid Banner/Audio Imp was offered, but Appnexus doesn't support Audio." + "code": 2, + "message": "A hybrid Banner/Audio Imp was offered, but Appnexus doesn't support Audio." }], "rubicon": [{ - "code": 1, - "message": "The request exceeded the timeout allocated" + "code": 1, + "message": "The request exceeded the timeout allocated" }] } } @@ -413,16 +458,14 @@ The values will be numbers that indicate the minimum allowed size for the ad, as Example: ``` { - "imp": [ - { - ... - "banner": { - ... - } - "instl": 1, + "imp": [{ + ... + "banner": { ... } - ] + "instl": 1, + ... + }] "device": { ... "h": 640, From 24665e8341ce985de7b7524e35a63962ffe5146d Mon Sep 17 00:00:00 2001 From: Brandon Ling <51931757+blingster7@users.noreply.github.com> Date: Thu, 11 Jun 2020 14:10:50 -0400 Subject: [PATCH 112/603] Triplelift: Add SRA Support (#1347) --- adapters/triplelift/triplelift_test.go | 2 +- .../triplelifttest/exemplary/optional-params.json | 2 +- .../triplelift/triplelifttest/exemplary/simple-banner.json | 2 +- .../triplelift/triplelifttest/exemplary/simple-video.json | 6 +++--- .../triplelifttest/supplemental/badresponseext.json | 2 +- .../triplelifttest/supplemental/badstatuscode.json | 2 +- .../triplelifttest/supplemental/notgoodstatuscode.json | 2 +- config/config.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index 2d7ed04f51b..6fd2b506f8a 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -6,5 +6,5 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "triplelifttest", NewTripleliftBidder(nil, "http://tlx.3lift.net/s2s/auction?supplier_id=20")) + adapterstest.RunJSONBidderTest(t, "triplelifttest", NewTripleliftBidder(nil, "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20")) } diff --git a/adapters/triplelift/triplelifttest/exemplary/optional-params.json b/adapters/triplelift/triplelifttest/exemplary/optional-params.json index 0851bc096d7..90c8da5b3c1 100644 --- a/adapters/triplelift/triplelifttest/exemplary/optional-params.json +++ b/adapters/triplelift/triplelifttest/exemplary/optional-params.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/triplelift/triplelifttest/exemplary/simple-banner.json b/adapters/triplelift/triplelifttest/exemplary/simple-banner.json index ff680037a7e..156e07e37eb 100644 --- a/adapters/triplelift/triplelifttest/exemplary/simple-banner.json +++ b/adapters/triplelift/triplelifttest/exemplary/simple-banner.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/triplelift/triplelifttest/exemplary/simple-video.json b/adapters/triplelift/triplelifttest/exemplary/simple-video.json index 185446bd243..846c62b4d37 100644 --- a/adapters/triplelift/triplelifttest/exemplary/simple-video.json +++ b/adapters/triplelift/triplelifttest/exemplary/simple-video.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ @@ -85,7 +85,7 @@ "adomain": [ "foo.com" ], - "iurl": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "cid": "958", "crid": "29681110", "h": 250, @@ -122,7 +122,7 @@ "adomain": [ "foo.com" ], - "iurl": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "iurl": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "cid": "958", "crid": "29681110", "w": 300, diff --git a/adapters/triplelift/triplelifttest/supplemental/badresponseext.json b/adapters/triplelift/triplelifttest/supplemental/badresponseext.json index 324c05825c9..6c09448fc4a 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badresponseext.json +++ b/adapters/triplelift/triplelifttest/supplemental/badresponseext.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json b/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json index 15799616933..f24eb7998ed 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json +++ b/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json b/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json index 963db593776..bdcc0e3a666 100644 --- a/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json +++ b/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tlx.3lift.net/s2s/auction?supplier_id=20", + "uri": "http://tlx.3lift.net/s2s/auction?sra=1&supplier_id=20", "body": { "id": "test-request-id", "imp": [ diff --git a/config/config.go b/config/config.go index 07384f9d2d3..56b6a1ba88d 100755 --- a/config/config.go +++ b/config/config.go @@ -805,7 +805,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") - v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20") + v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20") v.SetDefault("adapters.ucfunnel.endpoint", "http://apac-hk-adx.aralego.com/prebid") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") From eb77b170618b581833a1029264a7b39027644e1a Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 15 Jun 2020 10:24:36 -0400 Subject: [PATCH 113/603] Privacy: Limit Ad Tracking (#1334) --- config/config.go | 13 ++ config/config_test.go | 3 + exchange/exchange.go | 10 +- exchange/exchange_test.go | 17 ++- .../exchangetest/lmt-featureflag-off.json | 63 +++++++++ exchange/exchangetest/lmt-featureflag-on.json | 61 +++++++++ exchange/utils.go | 25 +++- exchange/utils_test.go | 94 +++++++++++-- privacy/enforcement.go | 9 +- privacy/enforcement_test.go | 53 ++++++-- privacy/lmt/policy.go | 33 +++++ privacy/lmt/policy_test.go | 128 ++++++++++++++++++ 12 files changed, 473 insertions(+), 36 deletions(-) create mode 100644 exchange/exchangetest/lmt-featureflag-off.json create mode 100644 exchange/exchangetest/lmt-featureflag-on.json create mode 100644 privacy/lmt/policy.go create mode 100644 privacy/lmt/policy_test.go diff --git a/config/config.go b/config/config.go index 56b6a1ba88d..0f470c6a611 100755 --- a/config/config.go +++ b/config/config.go @@ -49,6 +49,7 @@ type Configuration struct { AMPTimeoutAdjustment int64 `mapstructure:"amp_timeout_adjustment_ms"` GDPR GDPR `mapstructure:"gdpr"` CCPA CCPA `mapstructure:"ccpa"` + LMT LMT `mapstructure:"lmt"` CurrencyConverter CurrencyConverter `mapstructure:"currency_converter"` DefReqConfig DefReqConfig `mapstructure:"default_request"` @@ -139,6 +140,13 @@ func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Du return requested } +// Privacy is a grouping of privacy related configs to assist in dependency injection. +type Privacy struct { + CCPA CCPA + GDPR GDPR + LMT LMT +} + type GDPR struct { HostVendorID int `mapstructure:"host_vendor_id"` UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` @@ -193,6 +201,10 @@ type CCPA struct { Enforce bool `mapstructure:"enforce"` } +type LMT struct { + Enforce bool `mapstructure:"enforce"` +} + type Analytics struct { File FileLogs `mapstructure:"file"` } @@ -836,6 +848,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("ccpa.enforce", false) + v.SetDefault("lmt.enforce", true) v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // fetch currency rates every 30 minutes v.SetDefault("default_request.type", "") diff --git a/config/config_test.go b/config/config_test.go index ee8e68e7025..2b291fe978d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -43,6 +43,8 @@ gdpr: non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true +lmt: + enforce: true host_cookie: cookie_name: userid family: prebid @@ -240,6 +242,7 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false) cmpBools(t, "ccpa.enforce", cfg.CCPA.Enforce, true) + cmpBools(t, "lmt.enforce", cfg.LMT.Enforce, true) //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "blacklisted_apps", cfg.BlacklistedApps[0], "spamAppID") diff --git a/exchange/exchange.go b/exchange/exchange.go index 660beb641ef..84ae35d644c 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -48,7 +48,7 @@ type exchange struct { currencyConverter *currencies.RateConverter UsersyncIfAmbiguous bool defaultTTLs config.DefaultTTLs - enforceCCPA bool + privacyConfig config.Privacy } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -77,7 +77,11 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con e.currencyConverter = currencyConverter e.UsersyncIfAmbiguous = cfg.GDPR.UsersyncIfAmbiguous e.defaultTTLs = cfg.CacheURL.DefaultTTLs - e.enforceCCPA = cfg.CCPA.Enforce + e.privacyConfig = config.Privacy{ + CCPA: cfg.CCPA, + GDPR: cfg.GDPR, + LMT: cfg.LMT, + } return e } @@ -100,7 +104,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.enforceCCPA) + cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) // List of bidders we have requests for. liveAdapters := listBiddersWithRequests(cleanRequests) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index e9b2127e18b..4f329962a53 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -731,7 +731,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { if len(errs) != 0 { t.Fatalf("%s: Failed to parse aliases", filename) } - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, spec.EnforceCCPA) + + privacyConfig := config.Privacy{ + CCPA: config.CCPA{ + Enforce: spec.EnforceCCPA, + }, + LMT: config.LMT{ + Enforce: spec.EnforceLMT, + }, + } + + ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") if error != nil { @@ -816,7 +826,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb.BidResponse } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, enforceCCPA bool) Exchange { +func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy) Exchange { adapters := make(map[openrtb_ext.BidderName]adaptedBidder) for _, bidderName := range openrtb_ext.BidderMap { if spec, ok := expectations[string(bidderName)]; ok { @@ -854,7 +864,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] gDPR: gdpr.AlwaysAllow{}, currencyConverter: currencies.NewRateConverterDefault(), UsersyncIfAmbiguous: false, - enforceCCPA: enforceCCPA, + privacyConfig: privacyConfig, } } @@ -1620,6 +1630,7 @@ type exchangeSpec struct { OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` Response exchangeResponse `json:"response,omitempty"` EnforceCCPA bool `json:"enforceCcpa"` + EnforceLMT bool `json:"enforceLmt"` DebugLog *DebugLog `json:"debuglog,omitempty"` } diff --git a/exchange/exchangetest/lmt-featureflag-off.json b/exchange/exchangetest/lmt-featureflag-off.json new file mode 100644 index 00000000000..9a15c87953e --- /dev/null +++ b/exchange/exchangetest/lmt-featureflag-off.json @@ -0,0 +1,63 @@ +{ + "enforceLmt": false, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "device": { + "lmt": 1 + }, + "user": { + "id": "some-id", + "buyeruid": "some-buyer-id" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "device": { + "lmt": 1 + }, + "user": { + "id": "some-id", + "buyeruid": "some-buyer-id" + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/lmt-featureflag-on.json b/exchange/exchangetest/lmt-featureflag-on.json new file mode 100644 index 00000000000..440f8c76472 --- /dev/null +++ b/exchange/exchangetest/lmt-featureflag-on.json @@ -0,0 +1,61 @@ +{ + "enforceLmt": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "device": { + "lmt": 1 + }, + "user": { + "id": "some-id", + "buyeruid": "some-buyer-id" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "device": { + "lmt": 1 + }, + "user": { + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index f602d1e8fba..54122d13c09 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -8,11 +8,13 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/lmt" ) // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: @@ -26,8 +28,8 @@ func cleanOpenRTBRequests(ctx context.Context, blables map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, labels pbsmetrics.Labels, gDPR gdpr.Permissions, - usersyncIfAmbiguous, - enforceCCPA bool) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) { + usersyncIfAmbiguous bool, + privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) { impsByBidder, errs := splitImps(orig.Imp) if len(errs) > 0 { @@ -45,15 +47,24 @@ func cleanOpenRTBRequests(ctx context.Context, consent := extractConsent(orig) ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException() - privacyEnforcement := privacy.Enforcement{ - COPPA: orig.Regs != nil && orig.Regs.COPPA == 1, + var ccpaPolicy ccpa.Policy + if privacyConfig.CCPA.Enforce { + ccpaPolicy, _ = ccpa.ReadPolicy(orig) + } + + var lmtPolicy lmt.Policy + if privacyConfig.LMT.Enforce { + lmtPolicy = lmt.ReadPolicy(orig) } - if enforceCCPA { - ccpaPolicy, _ := ccpa.ReadPolicy(orig) - privacyEnforcement.CCPA = ccpaPolicy.ShouldEnforce() + // request level privacy policies + privacyEnforcement := privacy.Enforcement{ + CCPA: ccpaPolicy.ShouldEnforce(), + COPPA: orig.Regs != nil && orig.Regs.COPPA == 1, + LMT: lmtPolicy.ShouldEnforce(), } + // bidder level privacy policies for bidder, bidReq := range requestsByBidder { if gdpr == 1 { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index acbf25ff691..4dad3f54648 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/stretchr/testify/assert" @@ -69,8 +70,17 @@ func TestCleanOpenRTBRequests(t *testing.T) { applyCOPPA: false, consentedVendors: map[string]bool{"appnexus": true, "brightroll": true}}, } + privacyConfig := config.Privacy{ + CCPA: config.CCPA{ + Enforce: true, + }, + LMT: config.LMT{ + Enforce: true, + }, + } + for _, test := range testCases { - reqByBidders, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, true) + reqByBidders, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -99,9 +109,80 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { } for _, test := range testCases { - req := newCCPABidRequest(t) + req := newBidRequest(t) + req.Regs = &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`), + } - results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, test.enforceCCPA) + privacyConfig := config.Privacy{ + CCPA: config.CCPA{ + Enforce: test.enforceCCPA, + }, + } + + results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + result := results["appnexus"] + + assert.Nil(t, errs) + + if test.expectDataScrub { + assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") + } else { + assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") + } + } +} + +func TestCleanOpenRTBRequestsLMT(t *testing.T) { + var ( + enabled int8 = 1 + disabled int8 = 0 + ) + testCases := []struct { + description string + lmt *int8 + enforceLMT bool + expectDataScrub bool + }{ + { + description: "Feature Flag Enabled - OpenTRB Enabled", + lmt: &enabled, + enforceLMT: true, + expectDataScrub: true, + }, + { + description: "Feature Flag Disabled - OpenTRB Enabled", + lmt: &enabled, + enforceLMT: false, + expectDataScrub: false, + }, + { + description: "Feature Flag Enabled - OpenTRB Disabled", + lmt: &disabled, + enforceLMT: true, + expectDataScrub: false, + }, + { + description: "Feature Flag Disabled - OpenTRB Disabled", + lmt: &disabled, + enforceLMT: false, + expectDataScrub: false, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Device.Lmt = test.lmt + + privacyConfig := config.Privacy{ + LMT: config.LMT{ + Enforce: test.enforceLMT, + }, + } + + results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) @@ -163,8 +244,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { } } -func newCCPABidRequest(t *testing.T) *openrtb.BidRequest { - dnt := int8(1) +func newBidRequest(t *testing.T) *openrtb.BidRequest { return &openrtb.BidRequest{ Site: &openrtb.Site{ Page: "www.some.domain.com", @@ -178,7 +258,6 @@ func newCCPABidRequest(t *testing.T) *openrtb.BidRequest { UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", IFA: "ifa", IP: "132.173.230.74", - DNT: &dnt, Language: "EN", }, Source: &openrtb.Source{ @@ -189,9 +268,6 @@ func newCCPABidRequest(t *testing.T) *openrtb.BidRequest { BuyerUID: "their-id", Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`), - }, Imp: []openrtb.Imp{{ ID: "some-imp-id", Banner: &openrtb.Banner{ diff --git a/privacy/enforcement.go b/privacy/enforcement.go index d302192ec3f..8a5d201fc95 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -10,11 +10,12 @@ type Enforcement struct { COPPA bool GDPR bool GDPRGeo bool + LMT bool } // Any returns true if at least one privacy policy requires enforcement. func (e Enforcement) Any() bool { - return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo + return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.LMT } // Apply cleans personally identifiable information from an OpenRTB bid request. @@ -34,7 +35,7 @@ func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { return ScrubStrategyIPV6Lowest32 } - if e.GDPR || e.CCPA { + if e.GDPR || e.CCPA || e.LMT { return ScrubStrategyIPV6Lowest16 } @@ -46,7 +47,7 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { return ScrubStrategyGeoFull } - if e.GDPRGeo || e.CCPA { + if e.GDPRGeo || e.CCPA || e.LMT { return ScrubStrategyGeoReducedPrecision } @@ -63,7 +64,7 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs } // If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above) - if e.CCPA || e.GDPR { + if e.CCPA || e.GDPR || e.LMT { return ScrubStrategyUserID } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 0e82648d4b9..968c6354710 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -21,6 +21,7 @@ func TestAny(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + LMT: false, }, expected: false, }, @@ -31,6 +32,7 @@ func TestAny(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + LMT: true, }, expected: true, }, @@ -41,16 +43,7 @@ func TestAny(t *testing.T) { COPPA: true, GDPR: false, GDPRGeo: false, - }, - expected: true, - }, - { - description: "GDPRGeo only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPR: false, - GDPRGeo: true, + LMT: true, }, expected: true, }, @@ -79,6 +72,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + LMT: true, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -93,6 +87,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + LMT: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -107,6 +102,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: false, GDPRGeo: false, + LMT: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -121,6 +117,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: true, + LMT: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -135,6 +132,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: true, + LMT: false, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -149,6 +147,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + LMT: false, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -163,6 +162,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + LMT: false, }, ampGDPRException: true, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, @@ -177,6 +177,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: false, + LMT: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, @@ -191,6 +192,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: true, + LMT: false, }, ampGDPRException: false, expectedDeviceIPv6: ScrubStrategyIPV6None, @@ -198,6 +200,36 @@ func TestApply(t *testing.T) { expectedUser: ScrubStrategyUserNone, expectedUserGeo: ScrubStrategyGeoReducedPrecision, }, + { + description: "LMT Only", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: false, + LMT: true, + }, + ampGDPRException: false, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedUser: ScrubStrategyUserID, + expectedUserGeo: ScrubStrategyGeoReducedPrecision, + }, + { + description: "LMT Only, ampGDPRException", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: false, + LMT: true, + }, + ampGDPRException: true, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedUser: ScrubStrategyUserID, + expectedUserGeo: ScrubStrategyGeoReducedPrecision, + }, } for _, test := range testCases { @@ -229,6 +261,7 @@ func TestApplyNoneApplicable(t *testing.T) { CCPA: false, COPPA: false, GDPR: false, + LMT: false, } enforcement.apply(req, false, m) diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go new file mode 100644 index 00000000000..79425bf59f7 --- /dev/null +++ b/privacy/lmt/policy.go @@ -0,0 +1,33 @@ +package lmt + +import ( + "github.com/mxmCherry/openrtb" +) + +const ( + trackingUnrestricted = 0 + trackingRestricted = 1 +) + +// Policy represents the LMT (Limit Ad Tracking) policy for an OpenRTB bid request. +type Policy struct { + Signal int + SignalProvided bool +} + +// ReadPolicy extracts the LMT (Limit Ad Tracking) policy from an OpenRTB bid request. +func ReadPolicy(req *openrtb.BidRequest) Policy { + policy := Policy{} + + if req != nil && req.Device != nil && req.Device.Lmt != nil { + policy.Signal = int(*req.Device.Lmt) + policy.SignalProvided = true + } + + return policy +} + +// ShouldEnforce returns true when the LMT (Limit Ad Tracking) policy is in effect. +func (p Policy) ShouldEnforce() bool { + return p.SignalProvided && p.Signal == trackingRestricted +} diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go new file mode 100644 index 00000000000..45de219a9bf --- /dev/null +++ b/privacy/lmt/policy_test.go @@ -0,0 +1,128 @@ +package lmt + +import ( + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/stretchr/testify/assert" +) + +func TestRead(t *testing.T) { + var one int8 = 1 + + testCases := []struct { + description string + request *openrtb.BidRequest + expectedPolicy Policy + }{ + { + description: "Nil Request", + request: nil, + expectedPolicy: Policy{ + Signal: 0, + SignalProvided: false, + }, + }, + { + description: "Nil Device", + request: &openrtb.BidRequest{ + Device: nil, + }, + expectedPolicy: Policy{ + Signal: 0, + SignalProvided: false, + }, + }, + { + description: "Nil Device.Lmt", + request: &openrtb.BidRequest{ + Device: &openrtb.Device{ + Lmt: nil, + }, + }, + expectedPolicy: Policy{ + Signal: 0, + SignalProvided: false, + }, + }, + { + description: "Enabled", + request: &openrtb.BidRequest{ + Device: &openrtb.Device{ + Lmt: &one, + }, + }, + expectedPolicy: Policy{ + Signal: 1, + SignalProvided: true, + }, + }, + } + + for _, test := range testCases { + p := ReadPolicy(test.request) + assert.Equal(t, test.expectedPolicy, p, test.description) + } +} + +func TestShouldEnforce(t *testing.T) { + testCases := []struct { + description string + policy Policy + expected bool + }{ + { + description: "Signal Not Provided - Zero", + policy: Policy{ + Signal: 0, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Not Provided - One", + policy: Policy{ + Signal: 1, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Not Provided - Other", + policy: Policy{ + Signal: 42, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Provided - Zero", + policy: Policy{ + Signal: 0, + SignalProvided: true, + }, + expected: false, + }, + { + description: "Signal Provided - One", + policy: Policy{ + Signal: 1, + SignalProvided: true, + }, + expected: true, + }, + { + description: "Signal Provided - Other", + policy: Policy{ + Signal: 42, + SignalProvided: true, + }, + expected: false, + }, + } + + for _, test := range testCases { + result := test.policy.ShouldEnforce() + assert.Equal(t, test.expected, result, test.description) + } +} From dd05c38f5b698441b8f5c07908506093b2290745 Mon Sep 17 00:00:00 2001 From: Richard Lee <14349+dlackty@users.noreply.github.com> Date: Mon, 15 Jun 2020 23:21:03 +0800 Subject: [PATCH 114/603] Avoid overriding AMP request original size with mutli-size (#1352) --- endpoints/openrtb2/amp_auction.go | 17 ++++++++++------- endpoints/openrtb2/amp_auction_test.go | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 586481ddfc5..2dcd572c63c 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -407,31 +407,34 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope } func makeFormatReplacement(overrideWidth uint64, overrideHeight uint64, width uint64, height uint64, multisize string) []openrtb.Format { + var formats []openrtb.Format if overrideWidth != 0 && overrideHeight != 0 { - return []openrtb.Format{{ + formats = []openrtb.Format{{ W: overrideWidth, H: overrideHeight, }} } else if overrideWidth != 0 && height != 0 { - return []openrtb.Format{{ + formats = []openrtb.Format{{ W: overrideWidth, H: height, }} } else if width != 0 && overrideHeight != 0 { - return []openrtb.Format{{ + formats = []openrtb.Format{{ W: width, H: overrideHeight, }} - } else if parsedSizes := parseMultisize(multisize); len(parsedSizes) != 0 { - return parsedSizes } else if width != 0 && height != 0 { - return []openrtb.Format{{ + formats = []openrtb.Format{{ W: width, H: height, }} } - return nil + if parsedSizes := parseMultisize(multisize); len(parsedSizes) != 0 { + formats = append(formats, parsedSizes...) + } + + return formats } func setWidths(formats []openrtb.Format, width uint64) { diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 289db3f48cb..731fd55e196 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -832,6 +832,24 @@ func TestMultisize(t *testing.T) { }.execute(t) } +func TestSizeWithMultisize(t *testing.T) { + formatOverrideSpec{ + width: 20, + height: 40, + multisize: "200x50,100x60", + expect: []openrtb.Format{{ + W: 20, + H: 40, + }, { + W: 200, + H: 50, + }, { + W: 100, + H: 60, + }}, + }.execute(t) +} + func TestHeightOnly(t *testing.T) { formatOverrideSpec{ height: 200, From 62fe413dad59ee0c6c95e410ae6ad26d8af304ea Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 17 Jun 2020 10:25:02 -0400 Subject: [PATCH 115/603] Extra logging for timeout notifications (#1349) --- exchange/bidder.go | 13 ++++++++ exchange/bidder_test.go | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/exchange/bidder.go b/exchange/bidder.go index f9b4a522343..df9f0a3bf1b 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -389,7 +389,20 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou } } else { bidder.me.RecordTimeoutNotice(false) + if bidder.DebugConfig.TimeoutNotification.Log { + msg := fmt.Sprintf("TimeoutNotification: Failed to make timeout request: method(%s), uri(%s), error(%s)", toReq.Method, toReq.Uri, err.Error()) + util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) + } + } + } else if bidder.DebugConfig.TimeoutNotification.Log { + reqJSON, err := json.Marshal(req) + var msg string + if err == nil { + msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request(%s)", errL[0].Error(), string(reqJSON)) + } else { + msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request marshal failed(%s)", errL[0].Error(), err.Error()) } + util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index fa04e6a4771..fff397f0084 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1229,6 +1229,64 @@ func TestSetAssetTypes(t *testing.T) { } } +func TestTimeoutNotificationOff(t *testing.T) { + respBody := "{\"bid\":false}" + respStatus := 200 + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + bidderImpl := ¬ifingBidder{ + notiRequest: adapters.RequestData{ + Method: "GET", + Uri: server.URL + "/notify/me", + Body: nil, + Headers: http.Header{}, + }, + } + bidder := &bidderAdapter{ + Bidder: bidderImpl, + Client: server.Client(), + DebugConfig: config.Debug{}, + me: &metricsConfig.DummyMetricsEngine{}, + } + if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { + t.Error("Failed to cast bidder to a TimeoutBidder") + } else { + bidder.doTimeoutNotification(tb, &adapters.RequestData{}) + } +} + +func TestTimeoutNotificationOn(t *testing.T) { + respBody := "{\"bid\":false}" + respStatus := 200 + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + bidderImpl := ¬ifingBidder{ + notiRequest: adapters.RequestData{ + Method: "GET", + Uri: server.URL + "/notify/me", + Body: nil, + Headers: http.Header{}, + }, + } + bidder := &bidderAdapter{ + Bidder: bidderImpl, + Client: server.Client(), + DebugConfig: config.Debug{ + TimeoutNotification: config.TimeoutNotification{ + Log: true, + }, + }, + me: &metricsConfig.DummyMetricsEngine{}, + } + if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { + t.Error("Failed to cast bidder to a TimeoutBidder") + } else { + bidder.doTimeoutNotification(tb, &adapters.RequestData{}) + } +} + type goodSingleBidder struct { bidRequest *openrtb.BidRequest httpRequest *adapters.RequestData @@ -1302,3 +1360,19 @@ func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externa bidder.httpResponse = response return nil, []error{errors.New("Can't make a response.")} } + +type notifingBidder struct { + notiRequest adapters.RequestData +} + +func (bidder *notifingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + return nil, nil +} + +func (bidder *notifingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + return nil, nil +} + +func (bidder *notifingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { + return &bidder.notiRequest, nil +} From 2d2ed0c6dcd984769d1a65edc15a96bfc4c69482 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Wed, 17 Jun 2020 18:32:47 +0100 Subject: [PATCH 116/603] Consumable: Correct bid type, should always be "banner". (#1359) --- adapters/consumable/consumable.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index 1fa23377319..243f1b8000b 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -268,8 +268,11 @@ func (a *ConsumableAdapter) MakeBids( //bid.referrer = utils.getTopWindowUrl(); bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: getMediaTypeForImp(getImp(bid.ImpID, internalRequest.Imp)), + Bid: &bid, + // Consumable units are always HTML, never VAST. + // From Prebid's point of view, this means that Consumable units + // are always "banners". + BidType: openrtb_ext.BidTypeBanner, }) } } @@ -303,16 +306,6 @@ func extractExtensions(impression openrtb.Imp) (*adapters.ExtImpBidder, *openrtb return &bidderExt, &consumableExt, nil } -func getMediaTypeForImp(imp *openrtb.Imp) openrtb_ext.BidType { - // TODO: Whatever logic we need here possibly as follows - may always be Video when we bid - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } else if imp.Video != nil { - return openrtb_ext.BidTypeVideo - } - return openrtb_ext.BidTypeVideo -} - func testConsumableBidder(testClock instant, endpoint string) *ConsumableAdapter { return &ConsumableAdapter{testClock, endpoint} } From 98417cb13f3181a1cfd1d1b8089ab17de3423467 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 17 Jun 2020 13:33:22 -0400 Subject: [PATCH 117/603] Build With Go 1.14 (#1350) --- .travis.yml | 3 +-- Dockerfile | 4 ++-- README.md | 5 +++-- go.mod | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea2c46c4374..692141f716c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: go go: - - '1.12' - '1.13' - - '1.14' + - '1.14.2' go_import_path: github.com/prebid/prebid-server diff --git a/Dockerfile b/Dockerfile index a8fea9c33f6..2c60b9e39b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget RUN cd /tmp && \ - wget https://dl.google.com/go/go1.12.7.linux-amd64.tar.gz && \ - tar -xf go1.12.7.linux-amd64.tar.gz && \ + wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \ + tar -xf go1.14.2.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ diff --git a/README.md b/README.md index a59bf5f6aa3..b69e7e76db4 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,10 @@ For more information, see: ## Installation -First install [Go 1.12](https://golang.org/doc/install) latest version. +First install [Go](https://golang.org/doc/install) version 1.13 or newer. + Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). -If using Go version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. +We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. Download and prepare Prebid Server: diff --git a/go.mod b/go.mod index 0224057e464..72bb9b74886 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prebid/prebid-server -go 1.12 +go 1.13 require ( github.com/BurntSushi/toml v0.3.1 // indirect From d1c81294a7d05bb626c9b8f8181d845b8cee7fe9 Mon Sep 17 00:00:00 2001 From: jmaynardxandr <46759873+jmaynardxandr@users.noreply.github.com> Date: Wed, 17 Jun 2020 11:24:33 -0700 Subject: [PATCH 118/603] Category mapping changes from product team. (#1348) --- static/adapter/appnexus/opts.json | 69 +- .../category-mapping/freewheel/freewheel.json | 2348 +++++++++-------- 2 files changed, 1234 insertions(+), 1183 deletions(-) diff --git a/static/adapter/appnexus/opts.json b/static/adapter/appnexus/opts.json index 7bb297e0b41..41ee3c8f313 100644 --- a/static/adapter/appnexus/opts.json +++ b/static/adapter/appnexus/opts.json @@ -5,13 +5,14 @@ "3": "IAB10-1", "4": "IAB2-3", "5": "IAB19-8", + "6": "IAB22-1", "7": "IAB18-1", - "8": "IAB14-1", + "8": "IAB12-3", "9": "IAB5-1", "10": "IAB4-5", "11": "IAB13-4", - "13": "IAB19-2", "12": "IAB8-7", + "13": "IAB9-7", "14": "IAB7-1", "15": "IAB20-18", "16": "IAB10-7", @@ -20,33 +21,79 @@ "19": "IAB18-4", "20": "IAB1-5", "21": "IAB1-6", - "22": "IAB19-28", + "22": "IAB3-4", "23": "IAB19-13", "24": "IAB22-2", "25": "IAB3-9", - "26": "IAB17-26", + "26": "IAB17-18", "27": "IAB19-6", "28": "IAB1-7", - "29": "IAB9-5", + "29": "IAB9-30", "30": "IAB20-7", "31": "IAB20-17", "32": "IAB7-32", "33": "IAB16-5", "34": "IAB19-34", + "35": "IAB11-5", + "36": "IAB12-3", "37": "IAB11-4", + "38": "IAB12-3", "39": "IAB9-30", "41": "IAB7-44", + "42": "IAB7-1", + "43": "IAB7-30", + "50": "IAB19-30", "51": "IAB17-12", + "52": "IAB19-30", "53": "IAB3-1", "55": "IAB13-2", + "56": "IAB19-30", + "57": "IAB19-30", + "58": "IAB7-39", + "59": "IAB22-1", + "60": "IAB7-39", "61": "IAB21-3", - "62": "IAB6-4", - "63": "IAB15-10", + "62": "IAB5-1", + "63": "IAB12-3", + "64": "IAB20-18", "65": "IAB11-2", + "66": "IAB17-18", "67": "IAB9-9", - "69": "IAB7-1", - "71": "IAB22-2", + "68": "IAB9-5", + "69": "IAB7-44", + "71": "IAB22-3", + "73": "IAB19-30", "74": "IAB8-5", - "87": "IAB3-7" - } + "78": "IAB22-1", + "85": "IAB12-2", + "86": "IAB22-3", + "87": "IAB11-3", + "112": "IAB7-32", + "113": "IAB7-32", + "114": "IAB7-32", + "115": "IAB7-32", + "118": "IAB9-5", + "119": "IAB9-5", + "120": "IAB9-5", + "121": "IAB9-5", + "122": "IAB9-5", + "123": "IAB9-5", + "124": "IAB9-5", + "125": "IAB9-5", + "126": "IAB9-5", + "127": "IAB22-1", + "132": "IAB1-2", + "133": "IAB19-30", + "137": "IAB3-9", + "138": "IAB19-3", + "140": "IAB2-3", + "141": "IAB2-1", + "142": "IAB2-3", + "143": "IAB17-13", + "166": "IAB11-4", + "175": "IAB3-1", + "176": "IAB13-4", + "182": "IAB8-9", + "183": "IAB3-5" + } } diff --git a/static/category-mapping/freewheel/freewheel.json b/static/category-mapping/freewheel/freewheel.json index 7eebcce0c98..1c4a4fa2471 100644 --- a/static/category-mapping/freewheel/freewheel.json +++ b/static/category-mapping/freewheel/freewheel.json @@ -3,1176 +3,1180 @@ "id": "404", "name": "Publishing" }, - "IAB1-2": { - "id": "392", - "name": "Entertainment" - }, - "IAB1-5": { - "id": "419", - "name": "Filmed Entertainment" - }, - "IAB1-6": { - "id": "392", - "name": "Entertainment" - }, - "IAB1-7": { - "id": "392", - "name": "Entertainment" - }, - "IAB2-1": { - "id": "399", - "name": "Automotive" - }, - "IAB2-2": { - "id": "399", - "name": "Automotive" - }, - "IAB2-3": { - "id": "399", - "name": "Automotive" - }, - "IAB2-4": { - "id": "399", - "name": "Automotive" - }, - "IAB2-5": { - "id": "399", - "name": "Automotive" - }, - "IAB2-6": { - "id": "399", - "name": "Automotive" - }, - "IAB2-7": { - "id": "399", - "name": "Automotive" - }, - "IAB2-8": { - "id": "399", - "name": "Automotive" - }, - "IAB2-9": { - "id": "399", - "name": "Automotive" - }, - "IAB2-10": { - "id": "399", - "name": "Automotive" - }, - "IAB2-11": { - "id": "399", - "name": "Automotive" - }, - "IAB2-12": { - "id": "399", - "name": "Automotive" - }, - "IAB2-13": { - "id": "399", - "name": "Automotive" - }, - "IAB2-14": { - "id": "399", - "name": "Automotive" - }, - "IAB2-15": { - "id": "399", - "name": "Automotive" - }, - "IAB2-16": { - "id": "399", - "name": "Automotive" - }, - "IAB2-17": { - "id": "399", - "name": "Automotive" - }, - "IAB2-18": { - "id": "399", - "name": "Automotive" - }, - "IAB2-19": { - "id": "399", - "name": "Automotive" - }, - "IAB2-20": { - "id": "399", - "name": "Automotive" - }, - "IAB2-21": { - "id": "399", - "name": "Automotive" - }, - "IAB2-22": { - "id": "399", - "name": "Automotive" - }, - "IAB2-23": { - "id": "399", - "name": "Automotive" - }, - "IAB3-1": { - "id": "393", - "name": "Business Services" - }, - "IAB3-2": { - "id": "393", - "name": "Business Services" - }, - "IAB3-3": { - "id": "393", - "name": "Business Services" - }, - "IAB3-4": { - "id": "409", - "name": "Computing Product" - }, - "IAB3-5": { - "id": "393", - "name": "Business Services" - }, - "IAB3-6": { - "id": "393", - "name": "Business Services" - }, - "IAB3-7": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB3-8": { - "id": "393", - "name": "Business Services" - }, - "IAB3-9": { - "id": "393", - "name": "Business Services" - }, - "IAB3-10": { - "id": "393", - "name": "Business Services" - }, - "IAB3-11": { - "id": "393", - "name": "Business Services" - }, - "IAB3-12": { - "id": "393", - "name": "Business Services" - }, - "IAB4-1": { - "id": "393", - "name": "Business Services" - }, - "IAB4-2": { - "id": "405", - "name": "Educational Services" - }, - "IAB4-3": { - "id": "405", - "name": "Educational Services" - }, - "IAB4-4": { - "id": "393", - "name": "Business Services" - }, - "IAB4-5": { - "id": "393", - "name": "Business Services" - }, - "IAB4-6": { - "id": "393", - "name": "Business Services" - }, - "IAB4-7": { - "id": "406", - "name": "Health Care Services" - }, - "IAB4-8": { - "id": "405", - "name": "Educational Services" - }, - "IAB4-9": { - "id": "417", - "name": "Telecommunications" - }, - "IAB4-10": { - "id": "429", - "name": "Military" - }, - "IAB4-11": { - "id": "393", - "name": "Business Services" - }, - "IAB5-1": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-2": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-3": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-4": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-5": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-6": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-7": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-8": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-9": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-10": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-11": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-12": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-13": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-14": { - "id": "405", - "name": "Educational Services" - }, - "IAB5-15": { - "id": "405", - "name": "Educational Services" - }, - "IAB7-1": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-2": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-3": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-4": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-5": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-6": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-7": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-8": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-9": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-10": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-11": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-12": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-13": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-14": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-15": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-16": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-17": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-18": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-19": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-20": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-21": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-22": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-23": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-24": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-25": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-26": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-27": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-28": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-29": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-30": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-31": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-32": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-33": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-34": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-35": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-36": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-37": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-38": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-39": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-40": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-41": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-42": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-43": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-44": { - "id": "406", - "name": "Health Care Services" - }, - "IAB7-45": { - "id": "406", - "name": "Health Care Services" - }, - "IAB8-1": { - "id": "394", - "name": "Food" - }, - "IAB8-2": { - "id": "394", - "name": "Food" - }, - "IAB8-3": { - "id": "394", - "name": "Food" - }, - "IAB8-4": { - "id": "394", - "name": "Food" - }, - "IAB8-5": { - "id": "400", - "name": "Beer/Wine/Liquor" - }, - "IAB8-6": { - "id": "401", - "name": "Beverages" - }, - "IAB8-7": { - "id": "394", - "name": "Food" - }, - "IAB8-8": { - "id": "394", - "name": "Food" - }, - "IAB8-9": { - "id": "407", - "name": "Restaurant/Fast Food" - }, - "IAB8-10": { - "id": "394", - "name": "Food" - }, - "IAB8-11": { - "id": "394", - "name": "Food" - }, - "IAB8-12": { - "id": "394", - "name": "Food" - }, - "IAB8-13": { - "id": "394", - "name": "Food" - }, - "IAB8-14": { - "id": "394", - "name": "Food" - }, - "IAB8-15": { - "id": "394", - "name": "Food" - }, - "IAB8-16": { - "id": "394", - "name": "Food" - }, - "IAB8-17": { - "id": "394", - "name": "Food" - }, - "IAB8-18": { - "id": "400", - "name": "Beer/Wine/Liquor" - }, - "IAB9-1": { - "id": "392", - "name": "Entertainment" - }, - "IAB9-3": { - "id": "418", - "name": "Jewelry" - }, - "IAB9-5": { - "id": "413", - "name": "Gaming" - }, - "IAB9-6": { - "id": "412", - "name": "Household Products" - }, - "IAB9-9": { - "id": "426", - "name": "Tobacco" - }, - "IAB9-11": { - "id": "404", - "name": "Publishing" - }, - "IAB9-15": { - "id": "404", - "name": "Publishing" - }, - "IAB9-16": { - "id": "392", - "name": "Entertainment" - }, - "IAB9-18": { - "id": "393", - "name": "Business Services" - }, - "IAB9-19": { - "id": "418", - "name": "Jewelry" - }, - "IAB9-23": { - "id": "424", - "name": "Photographic Equipment" - }, - "IAB9-24": { - "id": "392", - "name": "Entertainment" - }, - "IAB9-25": { - "id": "392", - "name": "Entertainment" - }, - "IAB9-30": { - "id": "413", - "name": "Gaming" - }, - "IAB10-1": { - "id": "415", - "name": "Appliances" - }, - "IAB10-5": { - "id": "434", - "name": "Home Furnishings" - }, - "IAB10-6": { - "id": "434", - "name": "Home Furnishings" - }, - "IAB10-7": { - "id": "434", - "name": "Home Furnishings" - }, - "IAB10-8": { - "id": "393", - "name": "Business Services" - }, - "IAB10-9": { - "id": "434", - "name": "Home Furnishings" - }, - "IAB11-1": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB11-2": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB11-3": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB11-4": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB11-5": { - "id": "398", - "name": "Government/Municipal" - }, - "IAB12-1": { - "id": "438", - "name": "News" - }, - "IAB12-2": { - "id": "438", - "name": "News" - }, - "IAB12-3": { - "id": "438", - "name": "News" - }, - "IAB13-1": { - "id": "393", - "name": "Business Services" - }, - "IAB13-2": { - "id": "393", - "name": "Business Services" - }, - "IAB13-3": { - "id": "438", - "name": "News" - }, - "IAB13-4": { - "id": "391", - "name": "Financial Services" - }, - "IAB13-5": { - "id": "393", - "name": "Business Services" - }, - "IAB13-6": { - "id": "436", - "name": "Insurance" - }, - "IAB13-7": { - "id": "393", - "name": "Business Services" - }, - "IAB13-8": { - "id": "393", - "name": "Business Services" - }, - "IAB13-9": { - "id": "393", - "name": "Business Services" - }, - "IAB13-10": { - "id": "393", - "name": "Business Services" - }, - "IAB13-11": { - "id": "393", - "name": "Business Services" - }, - "IAB13-12": { - "id": "393", - "name": "Business Services" - }, - "IAB16-1": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-2": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-3": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-4": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-5": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-6": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB16-7": { - "id": "423", - "name": "Pet Food/Supplies" - }, - "IAB17-1": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-2": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-3": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-4": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-5": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-6": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-7": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-8": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-9": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-10": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-11": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-12": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-13": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-14": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-15": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-16": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-17": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-18": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-19": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-20": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-21": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-22": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-23": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-24": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-25": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-26": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-27": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-28": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-29": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-30": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-31": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-32": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-33": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-34": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-35": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-36": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-37": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-38": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-39": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-40": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-41": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-42": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-43": { - "id": "425", - "name": "Professional Sports" - }, - "IAB17-44": { - "id": "425", - "name": "Professional Sports" - }, - "IAB18-1": { - "id": "411", - "name": "Cosmetics/Toiletries" - }, - "IAB18-2": { - "id": "397", - "name": "Apparel" - }, - "IAB18-3": { - "id": "397", - "name": "Apparel" - }, - "IAB18-4": { - "id": "418", - "name": "Jewelry" - }, - "IAB18-5": { - "id": "397", - "name": "Apparel" - }, - "IAB18-6": { - "id": "397", - "name": "Apparel" - }, - "IAB19-2": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-3": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-4": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-5": { - "id": "424", - "name": "Photographic Equipment" - }, - "IAB19-6": { - "id": "417", - "name": "Telecommunications" - }, - "IAB19-7": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-8": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-9": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-10": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-11": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-12": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-13": { - "id": "404", - "name": "Publishing" - }, - "IAB19-14": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-15": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-16": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-17": { - "id": "419", - "name": "Filmed Entertainment" - }, - "IAB19-18": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-19": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-20": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-21": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-22": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-23": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-24": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-25": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-26": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-27": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-28": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-29": { - "id": "392", - "name": "Entertainment" - }, - "IAB19-30": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-31": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-32": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-33": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-34": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-35": { - "id": "409", - "name": "Computing Product" - }, - "IAB19-36": { - "id": "409", - "name": "Computing Product" - }, - "IAB20-1": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-2": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-3": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-4": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-5": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-6": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-7": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-8": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-9": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-10": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-11": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-12": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-13": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-14": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-15": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-16": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-17": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-18": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-19": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-20": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-21": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-22": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-23": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-24": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-25": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-26": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB20-27": { - "id": "395", - "name": "Travel/Hotels/Airlines" - }, - "IAB21-1": { - "id": "416", - "name": "Real Estate" - }, - "IAB21-2": { - "id": "416", - "name": "Real Estate" - }, - "IAB21-3": { - "id": "416", - "name": "Real Estate" - }, - "IAB22-1": { - "id": "416", - "name": "Real Estate" - }, - "IAB22-2": { - "id": "416", - "name": "Real Estate" - }, - "IAB22-3": { - "id": "416", - "name": "Real Estate" - } + "IAB1-2": { + "id": "392", + "name": "Entertainment" + }, + "IAB1-5": { + "id": "419", + "name": "Filmed Entertainment" + }, + "IAB1-6": { + "id": "392", + "name": "Entertainment" + }, + "IAB1-7": { + "id": "392", + "name": "Entertainment" + }, + "IAB2-1": { + "id": "399", + "name": "Automotive" + }, + "IAB2-2": { + "id": "399", + "name": "Automotive" + }, + "IAB2-3": { + "id": "399", + "name": "Automotive" + }, + "IAB2-4": { + "id": "399", + "name": "Automotive" + }, + "IAB2-5": { + "id": "399", + "name": "Automotive" + }, + "IAB2-6": { + "id": "399", + "name": "Automotive" + }, + "IAB2-7": { + "id": "399", + "name": "Automotive" + }, + "IAB2-8": { + "id": "399", + "name": "Automotive" + }, + "IAB2-9": { + "id": "399", + "name": "Automotive" + }, + "IAB2-10": { + "id": "399", + "name": "Automotive" + }, + "IAB2-11": { + "id": "399", + "name": "Automotive" + }, + "IAB2-12": { + "id": "399", + "name": "Automotive" + }, + "IAB2-13": { + "id": "399", + "name": "Automotive" + }, + "IAB2-14": { + "id": "399", + "name": "Automotive" + }, + "IAB2-15": { + "id": "399", + "name": "Automotive" + }, + "IAB2-16": { + "id": "399", + "name": "Automotive" + }, + "IAB2-17": { + "id": "399", + "name": "Automotive" + }, + "IAB2-18": { + "id": "399", + "name": "Automotive" + }, + "IAB2-19": { + "id": "399", + "name": "Automotive" + }, + "IAB2-20": { + "id": "399", + "name": "Automotive" + }, + "IAB2-21": { + "id": "399", + "name": "Automotive" + }, + "IAB2-22": { + "id": "399", + "name": "Automotive" + }, + "IAB2-23": { + "id": "399", + "name": "Automotive" + }, + "IAB3-1": { + "id": "393", + "name": "Business Services" + }, + "IAB3-2": { + "id": "393", + "name": "Business Services" + }, + "IAB3-3": { + "id": "393", + "name": "Business Services" + }, + "IAB3-4": { + "id": "408", + "name": "Office Equipment/Supplies" + }, + "IAB3-5": { + "id": "390", + "name": "Manufacturing" + }, + "IAB3-6": { + "id": "393", + "name": "Business Services" + }, + "IAB3-7": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB3-8": { + "id": "393", + "name": "Business Services" + }, + "IAB3-9": { + "id": "393", + "name": "Business Services" + }, + "IAB3-10": { + "id": "393", + "name": "Business Services" + }, + "IAB3-11": { + "id": "393", + "name": "Business Services" + }, + "IAB3-12": { + "id": "393", + "name": "Business Services" + }, + "IAB4-1": { + "id": "393", + "name": "Business Services" + }, + "IAB4-2": { + "id": "405", + "name": "Educational Services" + }, + "IAB4-3": { + "id": "405", + "name": "Educational Services" + }, + "IAB4-4": { + "id": "393", + "name": "Business Services" + }, + "IAB4-5": { + "id": "393", + "name": "Business Services" + }, + "IAB4-6": { + "id": "393", + "name": "Business Services" + }, + "IAB4-7": { + "id": "406", + "name": "Health Care Services" + }, + "IAB4-8": { + "id": "405", + "name": "Educational Services" + }, + "IAB4-9": { + "id": "417", + "name": "Telecommunications" + }, + "IAB4-10": { + "id": "429", + "name": "Military" + }, + "IAB4-11": { + "id": "393", + "name": "Business Services" + }, + "IAB5-1": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-2": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-3": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-4": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-5": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-6": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-7": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-8": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-9": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-10": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-11": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-12": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-13": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-14": { + "id": "405", + "name": "Educational Services" + }, + "IAB5-15": { + "id": "405", + "name": "Educational Services" + }, + "IAB7-1": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-2": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-3": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-4": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-5": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-6": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-7": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-8": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-9": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-10": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-11": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-12": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-13": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-14": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-15": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-16": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-17": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-18": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-19": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-20": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-21": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-22": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-23": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-24": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-25": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-26": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-27": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-28": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-29": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-30": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-31": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-32": { + "id": "402", + "name": "Pharmaceuticals" + }, + "IAB7-33": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-34": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-35": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-36": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-37": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-38": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-39": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-40": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-41": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-42": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-43": { + "id": "406", + "name": "Health Care Services" + }, + "IAB7-44": { + "id": "433", + "name": "Drug Stores" + }, + "IAB7-45": { + "id": "406", + "name": "Health Care Services" + }, + "IAB8-1": { + "id": "394", + "name": "Food" + }, + "IAB8-2": { + "id": "394", + "name": "Food" + }, + "IAB8-3": { + "id": "394", + "name": "Food" + }, + "IAB8-4": { + "id": "394", + "name": "Food" + }, + "IAB8-5": { + "id": "400", + "name": "Beer/Wine/Liquor" + }, + "IAB8-6": { + "id": "401", + "name": "Beverages" + }, + "IAB8-7": { + "id": "394", + "name": "Food" + }, + "IAB8-8": { + "id": "394", + "name": "Food" + }, + "IAB8-9": { + "id": "407", + "name": "Restaurant/Fast Food" + }, + "IAB8-10": { + "id": "394", + "name": "Food" + }, + "IAB8-11": { + "id": "394", + "name": "Food" + }, + "IAB8-12": { + "id": "394", + "name": "Food" + }, + "IAB8-13": { + "id": "394", + "name": "Food" + }, + "IAB8-14": { + "id": "394", + "name": "Food" + }, + "IAB8-15": { + "id": "394", + "name": "Food" + }, + "IAB8-16": { + "id": "394", + "name": "Food" + }, + "IAB8-17": { + "id": "394", + "name": "Food" + }, + "IAB8-18": { + "id": "400", + "name": "Beer/Wine/Liquor" + }, + "IAB9-1": { + "id": "392", + "name": "Entertainment" + }, + "IAB9-3": { + "id": "418", + "name": "Jewelry" + }, + "IAB9-5": { + "id": "414", + "name": "Gambling" + }, + "IAB9-6": { + "id": "412", + "name": "Household Products" + }, + "IAB9-7": { + "id": "413", + "name": "Gaming" + }, + "IAB9-9": { + "id": "426", + "name": "Tobacco" + }, + "IAB9-11": { + "id": "404", + "name": "Publishing" + }, + "IAB9-15": { + "id": "404", + "name": "Publishing" + }, + "IAB9-16": { + "id": "392", + "name": "Entertainment" + }, + "IAB9-18": { + "id": "393", + "name": "Business Services" + }, + "IAB9-19": { + "id": "418", + "name": "Jewelry" + }, + "IAB9-23": { + "id": "424", + "name": "Photographic Equipment" + }, + "IAB9-24": { + "id": "392", + "name": "Entertainment" + }, + "IAB9-25": { + "id": "392", + "name": "Entertainment" + }, + "IAB9-30": { + "id": "427", + "name": "Toys/Games" + }, + "IAB10-1": { + "id": "415", + "name": "Appliances" + }, + "IAB10-5": { + "id": "434", + "name": "Home Furnishings" + }, + "IAB10-6": { + "id": "434", + "name": "Home Furnishings" + }, + "IAB10-7": { + "id": "434", + "name": "Home Furnishings" + }, + "IAB10-8": { + "id": "393", + "name": "Business Services" + }, + "IAB10-9": { + "id": "434", + "name": "Home Furnishings" + }, + "IAB11-1": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB11-2": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB11-3": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB11-4": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB11-5": { + "id": "421", + "name": "Associations" + }, + "IAB12-1": { + "id": "438", + "name": "News" + }, + "IAB12-2": { + "id": "438", + "name": "News" + }, + "IAB12-3": { + "id": "438", + "name": "News" + }, + "IAB13-1": { + "id": "393", + "name": "Business Services" + }, + "IAB13-2": { + "id": "393", + "name": "Business Services" + }, + "IAB13-3": { + "id": "438", + "name": "News" + }, + "IAB13-4": { + "id": "391", + "name": "Financial Services" + }, + "IAB13-5": { + "id": "393", + "name": "Business Services" + }, + "IAB13-6": { + "id": "436", + "name": "Insurance" + }, + "IAB13-7": { + "id": "393", + "name": "Business Services" + }, + "IAB13-8": { + "id": "393", + "name": "Business Services" + }, + "IAB13-9": { + "id": "393", + "name": "Business Services" + }, + "IAB13-10": { + "id": "393", + "name": "Business Services" + }, + "IAB13-11": { + "id": "393", + "name": "Business Services" + }, + "IAB13-12": { + "id": "393", + "name": "Business Services" + }, + "IAB16-1": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-2": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-3": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-4": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-5": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-6": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB16-7": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB17-1": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-2": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-3": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-4": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-5": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-6": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-7": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-8": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-9": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-10": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-11": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-12": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-13": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-14": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-15": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-16": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-17": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-18": { + "id": "412", + "name": "Household Products" + }, + "IAB17-19": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-20": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-21": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-22": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-23": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-24": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-25": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-26": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-27": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-28": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-29": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-30": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-31": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-32": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-33": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-34": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-35": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-36": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-37": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-38": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-39": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-40": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-41": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-42": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-43": { + "id": "425", + "name": "Professional Sports" + }, + "IAB17-44": { + "id": "425", + "name": "Professional Sports" + }, + "IAB18-1": { + "id": "411", + "name": "Cosmetics/Toiletries" + }, + "IAB18-2": { + "id": "397", + "name": "Apparel" + }, + "IAB18-3": { + "id": "397", + "name": "Apparel" + }, + "IAB18-4": { + "id": "418", + "name": "Jewelry" + }, + "IAB18-5": { + "id": "397", + "name": "Apparel" + }, + "IAB18-6": { + "id": "397", + "name": "Apparel" + }, + "IAB19-2": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-3": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-4": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-5": { + "id": "424", + "name": "Photographic Equipment" + }, + "IAB19-6": { + "id": "417", + "name": "Telecommunications" + }, + "IAB19-7": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-8": { + "id": "432", + "name": "Audio and Video Equipment" + }, + "IAB19-9": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-10": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-11": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-12": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-13": { + "id": "404", + "name": "Publishing" + }, + "IAB19-14": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-15": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-16": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-17": { + "id": "419", + "name": "Filmed Entertainment" + }, + "IAB19-18": { + "id": "431", + "name": "Computing" + }, + "IAB19-19": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-20": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-21": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-22": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-23": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-24": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-25": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-26": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-27": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-28": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-29": { + "id": "392", + "name": "Entertainment" + }, + "IAB19-30": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-31": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-32": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-33": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-34": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-35": { + "id": "409", + "name": "Computing Product" + }, + "IAB19-36": { + "id": "409", + "name": "Computing Product" + }, + "IAB20-1": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-2": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-3": { + "id": "428", + "name": "Aerospace" + }, + "IAB20-4": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-5": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-6": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-7": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-8": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-9": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-10": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-11": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-12": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-13": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-14": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-15": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-16": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-17": { + "id": "396", + "name": "Amusement and Recreation" + }, + "IAB20-18": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-19": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-20": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-21": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-22": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-23": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-24": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-25": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-26": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB20-27": { + "id": "395", + "name": "Travel/Hotels/Airlines" + }, + "IAB21-1": { + "id": "416", + "name": "Real Estate" + }, + "IAB21-2": { + "id": "416", + "name": "Real Estate" + }, + "IAB21-3": { + "id": "416", + "name": "Real Estate" + }, + "IAB22-1": { + "id": "403", + "name": "Retail Stores/Chains" + }, + "IAB22-2": { + "id": "403", + "name": "Retail Stores/Chains" + }, + "IAB22-3": { + "id": "410", + "name": "Product" + } } \ No newline at end of file From 6eed87311b4a5a2f05bcceb758296a6b798f4dfb Mon Sep 17 00:00:00 2001 From: Simon Critchley Date: Thu, 18 Jun 2020 15:08:06 +0100 Subject: [PATCH 119/603] Adds Avocet adapter (#1354) --- adapters/avocet/avocet.go | 124 ++++++++ adapters/avocet/avocet/exemplary/banner.json | 106 +++++++ adapters/avocet/avocet/exemplary/video.json | 104 +++++++ adapters/avocet/avocet_test.go | 301 +++++++++++++++++++ adapters/avocet/usersync.go | 12 + adapters/avocet/usersync_test.go | 35 +++ config/config.go | 2 + docs/bidders/avocet.md | 5 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_avocet.go | 7 + static/bidder-info/avocet.yaml | 11 + static/bidder-params/avocet.json | 24 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 15 files changed, 738 insertions(+) create mode 100644 adapters/avocet/avocet.go create mode 100644 adapters/avocet/avocet/exemplary/banner.json create mode 100644 adapters/avocet/avocet/exemplary/video.json create mode 100644 adapters/avocet/avocet_test.go create mode 100644 adapters/avocet/usersync.go create mode 100644 adapters/avocet/usersync_test.go create mode 100644 docs/bidders/avocet.md create mode 100644 openrtb_ext/imp_avocet.go create mode 100644 static/bidder-info/avocet.yaml create mode 100644 static/bidder-params/avocet.json diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go new file mode 100644 index 00000000000..918fc23e894 --- /dev/null +++ b/adapters/avocet/avocet.go @@ -0,0 +1,124 @@ +package avocet + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// AvocetAdapter implements a adapters.Bidder compatible with the Avocet advertising platform. +type AvocetAdapter struct { + // Endpoint is a http endpoint to use when making requests to the Avocet advertising platform. + Endpoint string +} + +func (a *AvocetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, nil + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + body, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.FailedToRequestBids{ + Message: err.Error(), + }} + } + reqData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.Endpoint, + Body: body, + Headers: headers, + } + return []*adapters.RequestData{reqData}, nil +} + +type avocetBidExt struct { + Avocet avocetBidExtension `json:"avocet"` +} + +type avocetBidExtension struct { + Duration int `json:"duration"` + DealPriority int `json:"deal_priority"` +} + +func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode != http.StatusOK { + var errStr string + if len(response.Body) > 0 { + errStr = string(response.Body) + } else { + errStr = "no response body" + } + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("received status code: %v error: %s", response.StatusCode, errStr), + }} + } + + var br openrtb.BidResponse + err := json.Unmarshal(response.Body, &br) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + var errs []error + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + for i := range br.SeatBid { + for j := range br.SeatBid[i].Bid { + var ext avocetBidExt + if len(br.SeatBid[i].Bid[j].Ext) > 0 { + err := json.Unmarshal(br.SeatBid[i].Bid[j].Ext, &ext) + if err != nil { + errs = append(errs, err) + continue + } + } + tbid := &adapters.TypedBid{ + Bid: &br.SeatBid[i].Bid[j], + DealPriority: ext.Avocet.DealPriority, + } + tbid.BidType = getBidType(br.SeatBid[i].Bid[j], ext) + if tbid.BidType == openrtb_ext.BidTypeVideo { + tbid.BidVideo = &openrtb_ext.ExtBidPrebidVideo{ + Duration: ext.Avocet.Duration, + } + } + bidResponse.Bids = append(bidResponse.Bids, tbid) + } + } + return bidResponse, nil +} + +// getBidType returns the openrtb_ext.BidType for the provided bid. +func getBidType(bid openrtb.Bid, ext avocetBidExt) openrtb_ext.BidType { + if ext.Avocet.Duration != 0 { + return openrtb_ext.BidTypeVideo + } + switch bid.API { + case openrtb.APIFrameworkVPAID10, openrtb.APIFrameworkVPAID20: + return openrtb_ext.BidTypeVideo + default: + return openrtb_ext.BidTypeBanner + } +} + +// NewAvocetAdapter returns a new AvocetAdapter using the provided endpoint. +func NewAvocetAdapter(endpoint string) *AvocetAdapter { + return &AvocetAdapter{ + Endpoint: endpoint, + } +} diff --git a/adapters/avocet/avocet/exemplary/banner.json b/adapters/avocet/avocet/exemplary/banner.json new file mode 100644 index 00000000000..b5e308ea725 --- /dev/null +++ b/adapters/avocet/avocet/exemplary/banner.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5b51e49634f2021f127ff7c9", + "h": 250, + "id": "bc708396-9202-437b-b726-08b9864cb8b8", + "impid": "test-imp-id", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + "language": "en", + "price": 15.64434783, + "w": 300 + } + ], + "seat": "TEST_SEAT_ID" + } + ] + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "adm": "", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5b51e49634f2021f127ff7c9", + "h": 250, + "id": "bc708396-9202-437b-b726-08b9864cb8b8", + "impid": "test-imp-id", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + "language": "en", + "price": 15.64434783, + "w": 300 + }, + "type": "banner" + } + ] +} diff --git a/adapters/avocet/avocet/exemplary/video.json b/adapters/avocet/avocet/exemplary/video.json new file mode 100644 index 00000000000..2398256b0dd --- /dev/null +++ b/adapters/avocet/avocet/exemplary/video.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "bidid": "a0eec3aa-f9f6-42fb-9aa4-f1b5656d4f42", + "id": "749d36d7-c993-455f-aefd-ffd8a7e3ccf", + "seatbid": [ + { + "bid": [ + { + "adm": "Avocet", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5ec530e32d57fe1100f17d87", + "h": 396, + "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + "impid": "dfp-ad--top-above-nav", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + "language": "en", + "price": 15.64434783, + "w": 600, + "ext": { + "avocet": { + "duration": 30 + } + } + } + ], + "seat": "TEST_SEAT_ID" + } + ] + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "adm": "Avocet", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5ec530e32d57fe1100f17d87", + "h": 396, + "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + "impid": "dfp-ad--top-above-nav", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + "language": "en", + "price": 15.64434783, + "w": 600, + "ext": { + "avocet": { + "duration": 30 + } + } + }, + "type": "video" + } + ] +} diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go new file mode 100644 index 00000000000..ff2159bf406 --- /dev/null +++ b/adapters/avocet/avocet_test.go @@ -0,0 +1,301 @@ +package avocet + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "avocet", NewAvocetAdapter("https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013")) +} + +func TestAvocetAdapter_MakeRequests(t *testing.T) { + type fields struct { + Endpoint string + } + type args struct { + request *openrtb.BidRequest + reqInfo *adapters.ExtraRequestInfo + } + type reqData []*adapters.RequestData + tests := []struct { + name string + fields fields + args args + want []*adapters.RequestData + wantErrs []error + }{ + { + name: "return nil if zero imps", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + &openrtb.BidRequest{}, + nil, + }, + want: nil, + wantErrs: nil, + }, + { + name: "makes POST request with JSON content", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + &openrtb.BidRequest{Imp: []openrtb.Imp{{}}}, + nil, + }, + want: reqData{ + &adapters.RequestData{ + Method: http.MethodPost, + Uri: "https://bid.avct.cloud", + Body: []byte(`{"id":"","imp":[{"id":""}]}`), + Headers: map[string][]string{ + "Accept": {"application/json"}, + "Content-Type": {"application/json;charset=utf-8"}, + }, + }, + }, + wantErrs: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &AvocetAdapter{ + Endpoint: tt.fields.Endpoint, + } + got, got1 := a.MakeRequests(tt.args.request, tt.args.reqInfo) + if len(got) != len(tt.want) { + t.Errorf("AvocetAdapter.MakeRequests() got %v requests, wanted %v requests", len(got), len(tt.want)) + } + if len(got) == len(tt.want) { + for i := range tt.want { + if !reflect.DeepEqual(got[i], tt.want[i]) { + t.Errorf("AvocetAdapter.MakeRequests() got = %v, want %v", got[i], tt.want[i]) + } + } + } + if !reflect.DeepEqual(got1, tt.wantErrs) { + t.Errorf("AvocetAdapter.MakeRequests() got1 = %v, want %v", got1, tt.wantErrs) + } + }) + } +} + +func TestAvocetAdapter_MakeBids(t *testing.T) { + type fields struct { + Endpoint string + } + type args struct { + internalRequest *openrtb.BidRequest + externalRequest *adapters.RequestData + response *adapters.ResponseData + } + tests := []struct { + name string + fields fields + args args + want *adapters.BidderResponse + errs []error + }{ + { + name: "204 No Content indicates no bids", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + nil, + nil, + &adapters.ResponseData{StatusCode: http.StatusNoContent}, + }, + want: nil, + errs: nil, + }, + { + name: "Non-200 return error", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + nil, + nil, + &adapters.ResponseData{StatusCode: http.StatusBadRequest, Body: []byte("message")}, + }, + want: nil, + errs: []error{&errortypes.BadServerResponse{Message: "received status code: 400 error: message"}}, + }, + { + name: "200 response containing banner bids", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + nil, + nil, + &adapters.ResponseData{StatusCode: http.StatusOK, Body: validBannerBidResponseBody}, + }, + want: &adapters.BidderResponse{ + Currency: "USD", + Bids: []*adapters.TypedBid{ + { + Bid: &validBannerBid, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + errs: nil, + }, + { + name: "200 response containing video bids", + fields: fields{Endpoint: "https://bid.avct.cloud"}, + args: args{ + nil, + nil, + &adapters.ResponseData{StatusCode: http.StatusOK, Body: validVideoBidResponseBody}, + }, + want: &adapters.BidderResponse{ + Currency: "USD", + Bids: []*adapters.TypedBid{ + { + Bid: &validVideoBid, + BidType: openrtb_ext.BidTypeVideo, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + }, + }, + }, + }, + errs: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &AvocetAdapter{ + Endpoint: tt.fields.Endpoint, + } + got, got1 := a.MakeBids(tt.args.internalRequest, tt.args.externalRequest, tt.args.response) + if !reflect.DeepEqual(got, tt.want) { + gotb, _ := json.Marshal(got) + wantb, _ := json.Marshal(tt.want) + t.Errorf("AvocetAdapter.MakeBids() got = %s, want %s", string(gotb), string(wantb)) + } + if !reflect.DeepEqual(got1, tt.errs) { + t.Errorf("AvocetAdapter.MakeBids() got1 = %v, want %v", got1, tt.errs) + } + }) + } +} + +func Test_getBidType(t *testing.T) { + type args struct { + bid openrtb.Bid + ext avocetBidExt + } + tests := []struct { + name string + args args + want openrtb_ext.BidType + }{ + { + name: "VPAID 1.0", + args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID10}, avocetBidExt{}}, + want: openrtb_ext.BidTypeVideo, + }, + { + name: "VPAID 2.0", + args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID20}, avocetBidExt{}}, + want: openrtb_ext.BidTypeVideo, + }, + { + name: "other", + args: args{openrtb.Bid{}, avocetBidExt{}}, + want: openrtb_ext.BidTypeBanner, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getBidType(tt.args.bid, tt.args.ext); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getBidType() = %v, want %v", got, tt.want) + } + }) + } +} + +var validBannerBid = openrtb.Bid{ + AdM: "", + ADomain: []string{"avocet.io"}, + CID: "5b51e2d689654741306813a4", + CrID: "5b51e49634f2021f127ff7c9", + H: 250, + ID: "bc708396-9202-437b-b726-08b9864cb8b8", + ImpID: "test-imp-id", + IURL: "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + Language: "en", + Price: 15.64434783, + W: 300, +} + +var validBannerBidResponseBody = []byte(`{ + "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5b51e49634f2021f127ff7c9", + "h": 250, + "id": "bc708396-9202-437b-b726-08b9864cb8b8", + "impid": "test-imp-id", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + "language": "en", + "price": 15.64434783, + "w": 300 + } + ], + "seat": "TEST_SEAT_ID" + } + ] +}`) + +var validVideoBid = openrtb.Bid{ + AdM: "Avocet", + ADomain: []string{"avocet.io"}, + CID: "5b51e2d689654741306813a4", + CrID: "5ec530e32d57fe1100f17d87", + H: 396, + ID: "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + ImpID: "dfp-ad--top-above-nav", + IURL: "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + Language: "en", + Price: 15.64434783, + W: 600, + Ext: []byte(`{"avocet":{"duration":30}}`), +} + +var validVideoBidResponseBody = []byte(`{ + "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "adm": "Avocet", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5ec530e32d57fe1100f17d87", + "h": 396, + "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + "impid": "dfp-ad--top-above-nav", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + "language": "en", + "price": 15.64434783, + "w": 600, + "ext": {"avocet":{"duration":30}} + } + ], + "seat": "TEST_SEAT_ID" + } + ] +}`) diff --git a/adapters/avocet/usersync.go b/adapters/avocet/usersync.go new file mode 100644 index 00000000000..f1075ab3c52 --- /dev/null +++ b/adapters/avocet/usersync.go @@ -0,0 +1,12 @@ +package avocet + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAvocetSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("avocet", 63, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go new file mode 100644 index 00000000000..8fba403f1b1 --- /dev/null +++ b/adapters/avocet/usersync_test.go @@ -0,0 +1,35 @@ +package avocet + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAvocetSyncer(t *testing.T) { + syncURL := "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAvocetSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "ConsentString", + }, + CCPA: ccpa.Policy{ + Value: "PrivacyString", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://ads.avct.cloud/getuid?&gdpr=1&gdpr_consent=ConsentString&us_privacy=PrivacyString&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D1%26gdpr_consent%3DConsentString%26uid%3D%7B%7BUUID%7D%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 63, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 0f470c6a611..01de9b1ab2e 100755 --- a/config/config.go +++ b/config/config.go @@ -567,6 +567,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -772,6 +773,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs v.SetDefault("adapters.appnexus.platform_id", "5") + v.SetDefault("adapters.avocet.disabled", true) v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") diff --git a/docs/bidders/avocet.md b/docs/bidders/avocet.md new file mode 100644 index 00000000000..6aa67391af4 --- /dev/null +++ b/docs/bidders/avocet.md @@ -0,0 +1,5 @@ +# Avocet Bidder + +Please contact Avocet at info@avocet.io if you would like to get started selling inventory via the Avocet platform. + +**Note:** Avocet is disabled by default. Please enable it in the app config if you wish to use it. This can be done by setting `adapters.avocet.disabled` to `false` and by setting `adapters.avocet.endpoint` to a valid Avocet endpoint url. \ No newline at end of file diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 2ea8f7fb648..6e771236fb7 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -25,6 +25,7 @@ import ( "github.com/prebid/prebid-server/adapters/applogy" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/avocet" "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/brightroll" @@ -106,6 +107,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAJA: aja.NewAJABidder(cfg.Adapters[string(openrtb_ext.BidderAJA)].Endpoint), openrtb_ext.BidderApplogy: applogy.NewApplogyBidder(cfg.Adapters[string(openrtb_ext.BidderApplogy)].Endpoint), openrtb_ext.BidderAppnexus: appnexus.NewAppNexusBidder(client, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), + openrtb_ext.BidderAvocet: avocet.NewAvocetAdapter(cfg.Adapters[string(openrtb_ext.BidderAvocet)].Endpoint), openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo), openrtb_ext.BidderBeintoo: beintoo.NewBeintooBidder(cfg.Adapters[string(openrtb_ext.BidderBeintoo)].Endpoint), openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 659c6616fea..416f36d135f 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -40,6 +40,7 @@ const ( BidderApplogy BidderName = "applogy" BidderAppnexus BidderName = "appnexus" BidderAdoppler BidderName = "adoppler" + BidderAvocet BidderName = "avocet" BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBrightroll BidderName = "brightroll" @@ -118,6 +119,7 @@ var BidderMap = map[string]BidderName{ "applogy": BidderApplogy, "appnexus": BidderAppnexus, "adoppler": BidderAdoppler, + "avocet": BidderAvocet, "beachfront": BidderBeachfront, "beintoo": BidderBeintoo, "brightroll": BidderBrightroll, diff --git a/openrtb_ext/imp_avocet.go b/openrtb_ext/imp_avocet.go new file mode 100644 index 00000000000..7c9ca8c6eed --- /dev/null +++ b/openrtb_ext/imp_avocet.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpAvocet defines the contract for bidrequest.imp[i].ext.avocet +type ExtImpAvocet struct { + Placement string `json:"placement,omitempty"` + PlacementCode string `json:"placement_code,omitempty"` +} diff --git a/static/bidder-info/avocet.yaml b/static/bidder-info/avocet.yaml new file mode 100644 index 00000000000..ea98982d69c --- /dev/null +++ b/static/bidder-info/avocet.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "developers@avocet.io" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/avocet.json b/static/bidder-params/avocet.json new file mode 100644 index 00000000000..f27e5950f7c --- /dev/null +++ b/static/bidder-params/avocet.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Avocet Adapter Params", + "description": "A schema which validates params accepted by the Avocet adapter", + "type": "object", + "properties": { + "placement": { + "type": "string", + "description": "An Avocet placement ID" + }, + "placement_code": { + "type": "string", + "description": "An Avocet placement external code" + } + }, + "oneOf": [ + { + "required": ["placement"] + }, + { + "required": ["placement_code"] + } + ] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 751d2aabfbe..1beb9d586df 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/adapters/aja" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/avocet" "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/brightroll" @@ -90,6 +91,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAppnexus, appnexus.NewAppnexusSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAvocet, avocet.NewAvocetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index c9ef382fc92..69751dd55f4 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdvangelists): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, string(openrtb_ext.BidderAppnexus): syncConfig, + string(openrtb_ext.BidderAvocet): syncConfig, string(openrtb_ext.BidderBeachfront): syncConfig, string(openrtb_ext.BidderBeintoo): syncConfig, string(openrtb_ext.BidderBrightroll): syncConfig, From a8feeca97c88cd6ca41483fdba02123f5d40b691 Mon Sep 17 00:00:00 2001 From: Marcin Muras <47107445+mmuras@users.noreply.github.com> Date: Thu, 18 Jun 2020 17:27:42 +0200 Subject: [PATCH 120/603] AdOcean adapter - Support for sizes defined in prebid configuration. (#1339) support for multiple sizes bump version to 1.1.0 --- adapters/adocean/adocean.go | 155 ++++++++++++---- .../exemplary/multi-banner-impression.json | 5 +- .../exemplary/single-banner-impression.json | 2 +- .../supplemental/bad-response.json | 2 +- .../supplemental/encode-error.json | 2 +- .../supplemental/network-error.json | 2 +- .../adoceantest/supplemental/no-bid.json | 4 +- .../adoceantest/supplemental/no-sizes.json | 168 ++++++++++++++++++ .../supplemental/requests-merge.json | 4 +- 9 files changed, 298 insertions(+), 46 deletions(-) create mode 100644 adapters/adocean/adoceantest/supplemental/no-sizes.json diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 514aeb38424..8740712b6a0 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "regexp" + "sort" "strconv" "strings" "text/template" @@ -19,7 +20,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const adapterVersion = "1.0.0" +const adapterVersion = "1.1.0" const maxUriLength = 8000 const measurementCode = ` ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }, { + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-two", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }, { + "currency": "EUR", + "bids": [{ + "bid": { + "id": "adoceanmyaowafpdwlrks", + "impid": "ao-test-three", + "price": 1, + "adm": " ", + "crid": "0af345b42983cc4bc0", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adocean/adoceantest/supplemental/requests-merge.json b/adapters/adocean/adoceantest/supplemental/requests-merge.json index 9b5eb39aee2..e0736ec918f 100644 --- a/adapters/adocean/adoceantest/supplemental/requests-merge.json +++ b/adapters/adocean/adoceantest/supplemental/requests-merge.json @@ -83,7 +83,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0" }, "mockResponse": { "status": 200, @@ -117,7 +117,7 @@ } }, { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.0.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0" }, "mockResponse": { "status": 200, From 9c79ee485422e4e8383df8e288e5e20b95ec6841 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 18 Jun 2020 11:59:13 -0400 Subject: [PATCH 121/603] =?UTF-8?q?Log=20account=20id=20and=20all=20bidder?= =?UTF-8?q?=20names=20when=20recovering=20from=20OpenRTB=20auction=20bidde?= =?UTF-8?q?r=E2=80=A6=20(#1358)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exchange/exchange.go | 19 ++++++++++++++++--- exchange/exchange_test.go | 10 +++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 84ae35d644c..d7eab0f4475 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -303,7 +303,7 @@ func (e *exchange) getAllBids(ctx context.Context, cleanRequests map[openrtb_ext for bidderName, req := range cleanRequests { // Here we actually call the adapters and collect the bids. coreBidder := resolveBidder(string(bidderName), aliases) - bidderRunner := e.recoverSafely(func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) { + bidderRunner := e.recoverSafely(cleanRequests, func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) { // Passing in aName so a doesn't change out from under the go routine if bidlabels.Adapter == "" { glog.Errorf("Exchange: bidlables for %s (%s) missing adapter string", aName, coreBidder) @@ -373,11 +373,24 @@ func (e *exchange) getAllBids(ctx context.Context, cleanRequests map[openrtb_ext return adapterBids, adapterExtra, bidsFound } -func (e *exchange) recoverSafely(inner func(openrtb_ext.BidderName, openrtb_ext.BidderName, *openrtb.BidRequest, *pbsmetrics.AdapterLabels, currencies.Conversions), chBids chan *bidResponseWrapper) func(openrtb_ext.BidderName, openrtb_ext.BidderName, *openrtb.BidRequest, *pbsmetrics.AdapterLabels, currencies.Conversions) { +func (e *exchange) recoverSafely(cleanRequests map[openrtb_ext.BidderName]*openrtb.BidRequest, inner func(openrtb_ext.BidderName, openrtb_ext.BidderName, *openrtb.BidRequest, *pbsmetrics.AdapterLabels, currencies.Conversions), chBids chan *bidResponseWrapper) func(openrtb_ext.BidderName, openrtb_ext.BidderName, *openrtb.BidRequest, *pbsmetrics.AdapterLabels, currencies.Conversions) { return func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) { defer func() { if r := recover(); r != nil { - glog.Errorf("OpenRTB auction recovered panic from Bidder %s: %v. Stack trace is: %v", coreBidder, r, string(debug.Stack())) + + allBidders := "" + sb := strings.Builder{} + for k := range cleanRequests { + sb.WriteString(string(k)) + sb.WriteString(",") + } + if sb.Len() > 0 { + allBidders = sb.String()[:sb.Len()-1] + } + + glog.Errorf("OpenRTB auction recovered panic from Bidder %s: %v. "+ + "Account id: %s, All Bidders: %s, Stack trace is: %v", + coreBidder, r, bidlabels.PubID, allBidders, string(debug.Stack())) e.me.RecordAdapterPanic(*bidlabels) // Let the master request know that there is no data here brw := new(bidResponseWrapper) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 4f329962a53..93cb60fb5af 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -582,7 +582,15 @@ func TestPanicRecovery(t *testing.T) { panicker := func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) { panic("panic!") } - recovered := e.recoverSafely(panicker, chBids) + cleanReqs := map[openrtb_ext.BidderName]*openrtb.BidRequest{ + "bidder1": { + ID: "b-1", + }, + "bidder2": { + ID: "b-2", + }, + } + recovered := e.recoverSafely(cleanReqs, panicker, chBids) apnLabels := pbsmetrics.AdapterLabels{ Source: pbsmetrics.DemandWeb, RType: pbsmetrics.ReqTypeORTB2Web, From 5f39344c43d56331c69a855c979483a5e3d84086 Mon Sep 17 00:00:00 2001 From: tadam75 Date: Sat, 20 Jun 2020 19:22:25 +0200 Subject: [PATCH 122/603] Adding Smartadserver adapter (#1346) Co-authored-by: tadam --- adapters/smartadserver/params_test.go | 61 ++++++ adapters/smartadserver/smartadserver.go | 179 ++++++++++++++++++ adapters/smartadserver/smartadserver_test.go | 11 ++ .../exemplary/multi-banner.json | 175 +++++++++++++++++ .../exemplary/simple-banner.json | 94 +++++++++ .../exemplary/simple-video.json | 100 ++++++++++ .../smartadservertest/params/race/banner.json | 7 + .../smartadservertest/params/race/video.json | 7 + .../request-no-bidder-object.json | 21 ++ .../supplemental/request-no-ext-object.json | 19 ++ .../supplemental/request-no-imp.json | 13 ++ .../supplemental/request-site-recreated.json | 99 ++++++++++ .../response-200-without-body.json | 62 ++++++ .../supplemental/response-204.json | 56 ++++++ .../supplemental/response-400.json | 62 ++++++ .../supplemental/response-500.json | 62 ++++++ adapters/smartadserver/usersync.go | 12 ++ adapters/smartadserver/usersync_test.go | 35 ++++ config/config.go | 2 + docs/bidders/smartAdserver.md | 59 ++++++ exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_smartadserver.go | 9 + static/bidder-info/smartadserver.yaml | 11 ++ static/bidder-params/smartadserver.json | 35 ++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 27 files changed, 1198 insertions(+) create mode 100644 adapters/smartadserver/params_test.go create mode 100644 adapters/smartadserver/smartadserver.go create mode 100644 adapters/smartadserver/smartadserver_test.go create mode 100644 adapters/smartadserver/smartadservertest/exemplary/multi-banner.json create mode 100644 adapters/smartadserver/smartadservertest/exemplary/simple-banner.json create mode 100644 adapters/smartadserver/smartadservertest/exemplary/simple-video.json create mode 100644 adapters/smartadserver/smartadservertest/params/race/banner.json create mode 100644 adapters/smartadserver/smartadservertest/params/race/video.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/request-no-bidder-object.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/request-no-ext-object.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/request-no-imp.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/request-site-recreated.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/response-200-without-body.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/response-204.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/response-400.json create mode 100644 adapters/smartadserver/smartadservertest/supplemental/response-500.json create mode 100644 adapters/smartadserver/usersync.go create mode 100644 adapters/smartadserver/usersync_test.go create mode 100644 docs/bidders/smartAdserver.md create mode 100644 openrtb_ext/imp_smartadserver.go create mode 100644 static/bidder-info/smartadserver.yaml create mode 100644 static/bidder-params/smartadserver.json diff --git a/adapters/smartadserver/params_test.go b/adapters/smartadserver/params_test.go new file mode 100644 index 00000000000..6e45bb1d046 --- /dev/null +++ b/adapters/smartadserver/params_test.go @@ -0,0 +1,61 @@ +package smartadserver + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/smartadserver.json +// +// These also validate the format of the external API: request.imp[i].ext.smartadserver + +// TestValidParams makes sure that the smartadserver schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmartadserver, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected smartadserver params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the smartadserver schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmartadserver, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"networkId":73}`, + `{"networkId":73,"siteId":1,"pageId":2,"formatId":3}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"networkId":"73"}`, + `{"networkId":"73","siteId":"1","pageId":"2","formatId":"3"}`, + `{"siteId":1,"pageId":2,"formatId":3}`, + `{"networkId":73,"pageId":2,"formatId":3}`, + `{"networkId":73,"siteId":1,"formatId":3}`, + `{"networkId":73,"siteId":1,"pageId":2}`, +} diff --git a/adapters/smartadserver/smartadserver.go b/adapters/smartadserver/smartadserver.go new file mode 100644 index 00000000000..c35b749c51c --- /dev/null +++ b/adapters/smartadserver/smartadserver.go @@ -0,0 +1,179 @@ +package smartadserver + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strconv" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type SmartAdserverAdapter struct { + host string +} + +func NewSmartadserverBidder(host string) *SmartAdserverAdapter { + return &SmartAdserverAdapter{ + host: host, + } +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + + var adapterRequests []*adapters.RequestData + var errs []error + + // We copy the original request. + smartRequest := *request + + // We create or copy the Site object. + if smartRequest.Site == nil { + smartRequest.Site = &openrtb.Site{} + } else { + site := *smartRequest.Site + smartRequest.Site = &site + } + + // We create or copy the Publisher object. + if smartRequest.Site.Publisher == nil { + smartRequest.Site.Publisher = &openrtb.Publisher{} + } else { + publisher := *smartRequest.Site.Publisher + smartRequest.Site.Publisher = &publisher + } + + // We send one serialized "smartRequest" per impression of the original request. + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: "Error parsing bidderExt object", + }) + continue + } + + var smartadserverExt openrtb_ext.ExtImpSmartadserver + if err := json.Unmarshal(bidderExt.Bidder, &smartadserverExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: "Error parsing smartadserverExt parameters", + }) + continue + } + + // Adding publisher id. + smartRequest.Site.Publisher.ID = strconv.Itoa(smartadserverExt.NetworkID) + + // We send one request for each impression. + smartRequest.Imp = []openrtb.Imp{imp} + + var errMarshal error + if imp.Ext, errMarshal = json.Marshal(smartadserverExt); errMarshal != nil { + errs = append(errs, &errortypes.BadInput{ + Message: errMarshal.Error(), + }) + continue + } + + reqJSON, err := json.Marshal(smartRequest) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: "Error parsing reqJSON object", + }) + continue + } + + url, err := a.BuildEndpointURL(&smartadserverExt) + if url == "" { + errs = append(errs, err) + continue + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: headers, + }) + } + return adapterRequests, errs +} + +// MakeBids unpacks the server's response into Bids. +func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Unexpected status code: " + strconv.Itoa(response.StatusCode) + ". Run with request.debug = 1 for more info", + }} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + }) + + } + } + return bidResponse, []error{} +} + +// BuildEndpointURL : Builds endpoint url +func (a *SmartAdserverAdapter) BuildEndpointURL(params *openrtb_ext.ExtImpSmartadserver) (string, error) { + uri, err := url.Parse(a.host) + if err != nil || uri.Scheme == "" || uri.Host == "" { + return "", &errortypes.BadInput{ + Message: "Malformed URL: " + a.host + ".", + } + } + + uri.Path = path.Join(uri.Path, "api/bid") + uri.RawQuery = "callerId=5" + + return uri.String(), nil +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID { + if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/smartadserver/smartadserver_test.go b/adapters/smartadserver/smartadserver_test.go new file mode 100644 index 00000000000..7e4cff678cc --- /dev/null +++ b/adapters/smartadserver/smartadserver_test.go @@ -0,0 +1,11 @@ +package smartadserver + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "smartadservertest", NewSmartadserverBidder("https://ssb.smartadserver.com")) +} diff --git a/adapters/smartadserver/smartadservertest/exemplary/multi-banner.json b/adapters/smartadserver/smartadservertest/exemplary/multi-banner.json new file mode 100644 index 00000000000..b7cf27c37e2 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/exemplary/multi-banner.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [{"w": 300, "h": 150}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 4, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-multi-id", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-1", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + }] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-multi-id", + "imp": [ + { + "id": "test-imp-id-2", + "banner": { + "format": [{"w": 300, "h": 150}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 4, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-multi-id", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-2", + "price": 0.800000, + "adm": "some-test-ad", + "crid": "crid_11", + "h": 150, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-2", + "price": 0.8, + "adm": "some-test-ad", + "crid": "crid_11", + "w": 300, + "h": 150 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/exemplary/simple-banner.json b/adapters/smartadserver/smartadservertest/exemplary/simple-banner.json new file mode 100644 index 00000000000..e8faab141cd --- /dev/null +++ b/adapters/smartadserver/smartadservertest/exemplary/simple-banner.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/exemplary/simple-video.json b/adapters/smartadserver/smartadservertest/exemplary/simple-video.json new file mode 100644 index 00000000000..86f9361a807 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/exemplary/simple-video.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [ + { + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id-video", + "imp": [ + { + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-video", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 576, + "w": 1024 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/params/race/banner.json b/adapters/smartadserver/smartadservertest/params/race/banner.json new file mode 100644 index 00000000000..b34088307d4 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/params/race/banner.json @@ -0,0 +1,7 @@ +{ + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + \ No newline at end of file diff --git a/adapters/smartadserver/smartadservertest/params/race/video.json b/adapters/smartadserver/smartadservertest/params/race/video.json new file mode 100644 index 00000000000..b34088307d4 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/params/race/video.json @@ -0,0 +1,7 @@ +{ + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + \ No newline at end of file diff --git a/adapters/smartadserver/smartadservertest/supplemental/request-no-bidder-object.json b/adapters/smartadserver/smartadservertest/supplemental/request-no-bidder-object.json new file mode 100644 index 00000000000..48664a66073 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/request-no-bidder-object.json @@ -0,0 +1,21 @@ +{ + "mockBidRequest": { + "id": "test-no-bidder", + "imp": [ + { + "id": "test-imp-id-no-bidder", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error parsing smartadserverExt parameters", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/request-no-ext-object.json b/adapters/smartadserver/smartadservertest/supplemental/request-no-ext-object.json new file mode 100644 index 00000000000..d6637d0ebc3 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/request-no-ext-object.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-no-ext", + "imp": [ + { + "id": "test-imp-id-no-ext", + "banner": { + "format": [{"w": 728, "h": 90}] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error parsing bidderExt object", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/request-no-imp.json b/adapters/smartadserver/smartadservertest/supplemental/request-no-imp.json new file mode 100644 index 00000000000..50e00a8a969 --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/request-no-imp.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/request-site-recreated.json b/adapters/smartadserver/smartadservertest/supplemental/request-site-recreated.json new file mode 100644 index 00000000000..4a402674abf --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/request-site-recreated.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/response-200-without-body.json b/adapters/smartadserver/smartadservertest/supplemental/response-200-without-body.json new file mode 100644 index 00000000000..3e27569491c --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/response-200-without-body.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/response-204.json b/adapters/smartadserver/smartadservertest/supplemental/response-204.json new file mode 100644 index 00000000000..32aa2642f0a --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/response-204.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/response-400.json b/adapters/smartadserver/smartadservertest/supplemental/response-400.json new file mode 100644 index 00000000000..b7d5a95475d --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/response-400.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/smartadservertest/supplemental/response-500.json b/adapters/smartadserver/smartadservertest/supplemental/response-500.json new file mode 100644 index 00000000000..727e8f0843b --- /dev/null +++ b/adapters/smartadserver/smartadservertest/supplemental/response-500.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssb.smartadserver.com/api/bid?callerId=5", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": 1, + "pageId": 2, + "formatId": 3, + "networkId": 73 + } + } + } + ], + "site": { + "publisher": { + "id": "73" + } + } + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/smartadserver/usersync.go b/adapters/smartadserver/usersync.go new file mode 100644 index 00000000000..95b305ff227 --- /dev/null +++ b/adapters/smartadserver/usersync.go @@ -0,0 +1,12 @@ +package smartadserver + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmartadserverSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smartadserver", 45, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smartadserver/usersync_test.go b/adapters/smartadserver/usersync_test.go new file mode 100644 index 00000000000..e279b49e017 --- /dev/null +++ b/adapters/smartadserver/usersync_test.go @@ -0,0 +1,35 @@ +package smartadserver + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSmartadserverSyncer(t *testing.T) { + syncURL := "//ssbsync.smartadserver.com/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bsas_uid%5D%22" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSmartadserverSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA", + }, + CCPA: ccpa.Policy{ + Value: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//ssbsync.smartadserver.com/getuid?gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%5Bsas_uid%5D%22", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 45, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 01de9b1ab2e..c50118e2008 100755 --- a/config/config.go +++ b/config/config.go @@ -600,6 +600,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderRTBHouse doesn't have a good default. // openrtb_ext.BidderRubicon doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartadserver, "https://ssbsync.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") @@ -810,6 +811,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1") + v.SetDefault("adapters.smartadserver.endpoint", "https://ssb.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") diff --git a/docs/bidders/smartAdserver.md b/docs/bidders/smartAdserver.md new file mode 100644 index 00000000000..4d2663f8a3b --- /dev/null +++ b/docs/bidders/smartAdserver.md @@ -0,0 +1,59 @@ +# Smart Adserver Bidder + +## Parameters +The `ext.smartadserver` object of impression bid requests supports the following parameters : +- "networkId" - Required. The network identifier you have been provided with. +- "siteId" - Optional. The site identifier from your campaign configuration. +- "pageId" - Optional. The page identifier from your campaign configuration. +- "formatId" - Optional. The format identifier from your campaign configuration. + +The network identifier is provided by your Account Manager. +**Note:** The site, page and format identifiers have to all be provided or all empty. + +## Examples + +Without site/page/format : +``` + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "smartadserver": { + "networkId": 73 + } + } + }] +``` + +With site/page/format : + +``` + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "smartadserver": { + "networkId": 73 + "siteId": 1, + "pageId": 2, + "formatId": 3 + } + } + }] +``` \ No newline at end of file diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 6e771236fb7..44054df06fd 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -62,6 +62,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" @@ -150,6 +151,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), openrtb_ext.BidderSharethrough: sharethrough.NewSharethroughBidder(cfg.Adapters[string(openrtb_ext.BidderSharethrough)].Endpoint), + openrtb_ext.BidderSmartadserver: smartadserver.NewSmartadserverBidder(cfg.Adapters[string(openrtb_ext.BidderSmartadserver)].Endpoint), openrtb_ext.BidderSmartRTB: smartrtb.NewSmartRTBBidder(cfg.Adapters[string(openrtb_ext.BidderSmartRTB)].Endpoint), openrtb_ext.BidderSomoaudience: somoaudience.NewSomoaudienceBidder(cfg.Adapters[string(openrtb_ext.BidderSomoaudience)].Endpoint), openrtb_ext.BidderSonobi: sonobi.NewSonobiBidder(client, cfg.Adapters[string(openrtb_ext.BidderSonobi)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 416f36d135f..1f9cffb9938 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -78,6 +78,7 @@ const ( BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" + BidderSmartadserver BidderName = "smartadserver" BidderSmartRTB BidderName = "smartrtb" BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" @@ -157,6 +158,7 @@ var BidderMap = map[string]BidderName{ "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, "sharethrough": BidderSharethrough, + "smartadserver": BidderSmartadserver, "smartrtb": BidderSmartRTB, "somoaudience": BidderSomoaudience, "sonobi": BidderSonobi, diff --git a/openrtb_ext/imp_smartadserver.go b/openrtb_ext/imp_smartadserver.go new file mode 100644 index 00000000000..d542e0ffd27 --- /dev/null +++ b/openrtb_ext/imp_smartadserver.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpSmartadserver defines the contract for bidrequest.imp[i].ext.smartadserver +type ExtImpSmartadserver struct { + SiteID int `json:"siteId"` + PageID int `json:"pageId"` + FormatID int `json:"formatId"` + NetworkID int `json:"networkId"` +} diff --git a/static/bidder-info/smartadserver.yaml b/static/bidder-info/smartadserver.yaml new file mode 100644 index 00000000000..626b7dac00d --- /dev/null +++ b/static/bidder-info/smartadserver.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "support@smartadserver.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/smartadserver.json b/static/bidder-params/smartadserver.json new file mode 100644 index 00000000000..b76a3bd6ac9 --- /dev/null +++ b/static/bidder-params/smartadserver.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Smartadserver Adapter Params", + "description": "A schema which validates params accepted by the Smartadserver adapter", + + "type": "object", + "properties": { + "siteId": { + "type": "integer", + "description": "The site id.", + "minimum": 1 + }, + "pageId": { + "type": "integer", + "description": "The page id.", + "minimum": 1 + }, + "formatId": { + "type": "integer", + "description": "The format id.", + "minimum": 1 + }, + "networkId": { + "type": "integer", + "description": "The network id.", + "minimum": 1 + } + }, + "dependencies": { + "siteId": { "required": ["pageId", "formatId"] }, + "pageId": { "required": ["siteId", "formatId"] }, + "formatId": { "required": ["siteId", "pageId"] } + }, + "required": ["networkId"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1beb9d586df..5657c8b7010 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -50,6 +50,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" @@ -127,6 +128,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSomoaudience, somoaudience.NewSomoaudienceSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSonobi, sonobi.NewSonobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartadserver, smartadserver.NewSmartadserverSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 69751dd55f4..363cd491648 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -62,6 +62,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, + string(openrtb_ext.BidderSmartadserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, From 379492dced7d01da04c4762cb2d38ad9d02a37e4 Mon Sep 17 00:00:00 2001 From: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Date: Sat, 20 Jun 2020 13:26:09 -0700 Subject: [PATCH 123/603] Added additional Ext Param (#1357) Co-authored-by: Vinay Prasad --- adapters/telaria/telaria.go | 20 +- .../telariatest/exemplary/video-app.json | 226 ++++++++++-------- .../telariatest/exemplary/video-web.json | 224 +++++++++-------- openrtb_ext/imp_telaria.go | 7 +- 4 files changed, 277 insertions(+), 200 deletions(-) diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index 294d0d100a9..6bed043152e 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -23,6 +23,10 @@ type ImpressionExtOut struct { OriginalPublisherID string `json:"originalPublisherid"` } +type telariaBidExt struct { + Extra json.RawMessage `json:"extra,omitempty"` +} + // used for cookies and such func (a *TelariaAdapter) Name() string { return "telaria" @@ -186,15 +190,17 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *ad originalPublisherID := a.FetchOriginalPublisherID(&request) var errors []error + var telariaImpExt *openrtb_ext.ExtImpTelaria + var err error for i, imp := range request.Imp { // fetch adCode & seatCode from Imp[i].Ext - telariaExt, err := a.FetchTelariaExtImpParams(&imp) + telariaImpExt, err = a.FetchTelariaExtImpParams(&imp) if err != nil { errors = append(errors, err) break } - seatCode = telariaExt.SeatCode + seatCode = telariaImpExt.SeatCode // move the original tagId and the original publisher.id into the Imp[i].Ext object request.Imp[i].Ext, err = json.Marshal(&ImpressionExtOut{request.Imp[i].TagID, originalPublisherID}) @@ -204,7 +210,15 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *ad } // Swap the tagID with adCode - request.Imp[i].TagID = telariaExt.AdCode + request.Imp[i].TagID = telariaImpExt.AdCode + } + + // Add the Extra from Imp to the top level Ext + if telariaImpExt != nil && telariaImpExt.Extra != nil { + request.Ext, err = json.Marshal(&telariaBidExt{Extra: telariaImpExt.Extra}) + if err != nil { + errors = append(errors, err) + } } if len(errors) > 0 { diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json index fa755cc93d3..6450509c8e1 100644 --- a/adapters/telaria/telariatest/exemplary/video-app.json +++ b/adapters/telaria/telariatest/exemplary/video-app.json @@ -39,118 +39,148 @@ "ext": { "bidder": { "adCode": "my-adcode", - "seatCode": "my-seatcode" + "seatCode": "my-seatcode", + "extra": { + "custom": "1234" + } } } } ] }, - "httpCalls": [{ - "expectedRequest": { - "headers": { - "Content-Type": ["application/json;charset=utf-8"], - "Accept": ["application/json"], - "X-Openrtb-Version": ["2.5"], - "User-Agent": ["test-user-agent"], - "X-Forwarded-For": ["123.123.123.123"], - "Accept-Language": ["en"], - "Dnt": ["0"] - }, - "uri": "https://ads.tremorhub.com/ad/rtb/prebid", - "body": { - "id": "some-request-id", - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Accept-Language": [ + "en" + ], + "Dnt": [ + "0" + ] }, - "imp": [ - { - "id": "some-impression-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 120, - "maxduration": 150, - "w": 640, - "h": 480 - }, - "tagid": "my-adcode", - "ext": { - "originalTagid": "ogTAGID", - "originalPublisherid": "123456789" + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "ext": { + "extra": { + "custom": "1234" + } + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "my-adcode", + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } } - } - ], - "app": { - "id": "123456789", - "name": "Awesome App", - "bundle": "com.app.awesome", - "domain": "awesomeapp.com", - "cat": [ - "IAB22-1" ], - "publisher": { - "id": "my-seatcode" - } - }, - "user": { - "buyeruid": "awesome-user" - }, - "tmax": 1000 - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "awesome-resp-id", - "seatbid": [{ - "bid": [{ - "id": "a3ae1b4e2fc24a4fb45540082e98e161", - "impid": "1", - "price": 3.5, - "adm": "asesome-markup", - "adomain": [ - "awesome.com" + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" ], - "crid": "20", - "w": 1280, - "h": 720, - "ext": { - "prebid": { - "type": "video" - } + "publisher": { + "id": "my-seatcode" } - }], - "seat": "telaria" - }], - "cur": "USD", - "ext": { - "responsetimemillis": { - "telaria": 154 }, - "tmaxrequest": 1000 + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "telaria" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } } } } - }], - "expectedBids": [{ - "id": "a3ae1b4e2fc24a4fb45540082e98e161", - "impid": "1", - "price": 3.5, - "adm": "awesome-markup", - "adomain": [ - "awesome.com" - ], - "crid": "20", - "w": 1280, - "h": 720, - "ext": { - "prebid": { - "type": "video" + ], + "expectedBids": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } } } - }] + ] } diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json index c671d2f5f30..f3a3a56928c 100644 --- a/adapters/telaria/telariatest/exemplary/video-web.json +++ b/adapters/telaria/telariatest/exemplary/video-web.json @@ -1,4 +1,3 @@ - { "mockBidRequest": { "id": "some-request-id", @@ -23,7 +22,9 @@ "id": "some-impression-id", "tagid": "ogTAGID", "video": { - "mimes": ["video/mp4"], + "mimes": [ + "video/mp4" + ], "w": 640, "h": 480, "minduration": 120, @@ -32,113 +33,142 @@ "ext": { "bidder": { "adCode": "my-adcode", - "seatCode": "my-seatcode" + "seatCode": "my-seatcode", + "extra": { + "custom": "1234" + } } } } ] }, - - "httpCalls": [{ - "expectedRequest": { - "headers": { - "Content-Type": ["application/json;charset=utf-8"], - "Accept": ["application/json"], - "X-Openrtb-Version": ["2.5"], - "User-Agent": ["test-user-agent"], - "X-Forwarded-For": ["123.123.123.123"], - "Accept-Language": ["en"], - "Dnt": ["0"] - }, - "uri": "https://ads.tremorhub.com/ad/rtb/prebid", - "body": { - "id": "some-request-id", - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 - }, - "imp": [ - { - "id": "some-impression-id", - "tagid": "my-adcode", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 120, - "maxduration": 150, - "w": 640, - "h": 480 - }, - "ext": { - "originalTagid": "ogTAGID", - "originalPublisherid": "123456789" - } - } - ], - "site": { - "page": "test.com", - "publisher": { - "id": "my-seatcode" - } - }, - "user": { - "buyeruid": "awesome-user" + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Accept-Language": [ + "en" + ], + "Dnt": [ + "0" + ] }, - "tmax": 1000 - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "awesome-resp-id", - "seatbid": [{ - "bid": [{ - "id": "a3ae1b4e2fc24a4fb45540082e98e161", - "impid": "1", - "price": 3.5, - "adm": "asesome-markup", - "adomain": [ - "awesome.com" - ], - "crid": "20", - "w": 1280, - "h": 720, - "ext": { - "prebid": { - "type": "video" + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" } } - }], - "seat": "telaria" - }], - "cur": "USD", - "ext": { - "responsetimemillis": { - "telaria": 154 + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" }, - "tmaxrequest": 1000 + "tmax": 1000, + "ext": { + "extra": { + "custom": "1234" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "telaria" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } } } } - }], - "expectedBids": [{ - "id": "a3ae1b4e2fc24a4fb45540082e98e161", - "impid": "1", - "price": 3.5, - "adm": "asesome-markup", - "adomain": [ - "awesome.com" - ], - "crid": "20", - "w": 1280, - "h": 720, - "ext": { - "prebid": { - "type": "video" + ], + "expectedBids": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } } } - }] + ] } diff --git a/openrtb_ext/imp_telaria.go b/openrtb_ext/imp_telaria.go index 8ea371a8ad0..19a025c0b15 100644 --- a/openrtb_ext/imp_telaria.go +++ b/openrtb_ext/imp_telaria.go @@ -1,6 +1,9 @@ package openrtb_ext +import "encoding/json" + type ExtImpTelaria struct { - AdCode string `json:"adCode,omitempty"` - SeatCode string `json:"seatCode"` + AdCode string `json:"adCode,omitempty"` + SeatCode string `json:"seatCode"` + Extra json.RawMessage `json:"extra,omitempty"` } From aaff1568c385f2e9b1c7bda32f4c037d81db5199 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Wed, 24 Jun 2020 00:57:38 +0300 Subject: [PATCH 124/603] Adman adapter (#1356) Co-authored-by: Aiholkin --- adapters/adman/adman.go | 140 ++++++++++++++++++ adapters/adman/adman_test.go | 12 ++ .../admantest/exemplary/simple-banner.json | 134 +++++++++++++++++ .../admantest/exemplary/simple-video.json | 119 +++++++++++++++ .../exemplary/simple-web-banner.json | 133 +++++++++++++++++ adapters/adman/admantest/params/banner.json | 3 + .../adman/admantest/params/race/banner.json | 3 + .../adman/admantest/params/race/video.json | 3 + adapters/adman/admantest/params/video.json | 3 + .../admantest/supplemental/bad-imp-ext.json | 42 ++++++ .../admantest/supplemental/bad_response.json | 85 +++++++++++ .../admantest/supplemental/no-imp-ext-1.json | 39 +++++ .../admantest/supplemental/no-imp-ext-2.json | 39 +++++ .../admantest/supplemental/status-204.json | 79 ++++++++++ .../admantest/supplemental/status-404.json | 85 +++++++++++ adapters/adman/params_test.go | 46 ++++++ adapters/adman/usersync.go | 13 ++ adapters/adman/usersync_test.go | 35 +++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adman.go | 6 + static/bidder-info/adman.yaml | 11 ++ static/bidder-params/adman.json | 15 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 1054 insertions(+) create mode 100644 adapters/adman/adman.go create mode 100644 adapters/adman/adman_test.go create mode 100644 adapters/adman/admantest/exemplary/simple-banner.json create mode 100644 adapters/adman/admantest/exemplary/simple-video.json create mode 100644 adapters/adman/admantest/exemplary/simple-web-banner.json create mode 100644 adapters/adman/admantest/params/banner.json create mode 100644 adapters/adman/admantest/params/race/banner.json create mode 100644 adapters/adman/admantest/params/race/video.json create mode 100644 adapters/adman/admantest/params/video.json create mode 100644 adapters/adman/admantest/supplemental/bad-imp-ext.json create mode 100644 adapters/adman/admantest/supplemental/bad_response.json create mode 100644 adapters/adman/admantest/supplemental/no-imp-ext-1.json create mode 100644 adapters/adman/admantest/supplemental/no-imp-ext-2.json create mode 100644 adapters/adman/admantest/supplemental/status-204.json create mode 100644 adapters/adman/admantest/supplemental/status-404.json create mode 100644 adapters/adman/params_test.go create mode 100644 adapters/adman/usersync.go create mode 100644 adapters/adman/usersync_test.go create mode 100644 openrtb_ext/imp_adman.go create mode 100644 static/bidder-info/adman.yaml create mode 100644 static/bidder-params/adman.json diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go new file mode 100644 index 00000000000..aa8d0dc6e74 --- /dev/null +++ b/adapters/adman/adman.go @@ -0,0 +1,140 @@ +package adman + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// AdmanAdapter struct +type AdmanAdapter struct { + URI string +} + +// NewAdmanBidder Initializes the Bidder +func NewAdmanBidder(endpoint string) *AdmanAdapter { + return &AdmanAdapter{ + URI: endpoint, + } +} + +type admanParams struct { + TagID string `json:"TagID"` +} + +// MakeRequests create bid request for adman demand +func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var admanExt openrtb_ext.ExtImpAdman + var err error + + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb.Imp{imp} + + var bidderExt adapters.ExtImpBidder + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + + if err = json.Unmarshal(bidderExt.Bidder, &admanExt); err != nil { + errs = append(errs, err) + continue + } + + reqCopy.Imp[0].TagID = admanExt.TagID + + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + } + return adapterRequests, errs +} + +func (a *AdmanAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { + + var errs []error + + reqJSON, err := json.Marshal(request) + + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }, errs +} + +// MakeBids makes the bids +func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusNotFound { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType, nil + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + } +} diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go new file mode 100644 index 00000000000..da0f37e9a48 --- /dev/null +++ b/adapters/adman/adman_test.go @@ -0,0 +1,12 @@ +package adman + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + admanAdapter := NewAdmanBidder("http://eu-ams-1.admanmedia.com/?c=o&m=ortb") + adapterstest.RunJSONBidderTest(t, "admantest", admanAdapter) +} diff --git a/adapters/adman/admantest/exemplary/simple-banner.json b/adapters/adman/admantest/exemplary/simple-banner.json new file mode 100644 index 00000000000..41f76e00645 --- /dev/null +++ b/adapters/adman/admantest/exemplary/simple-banner.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": { + "bidder": { + "TagID": "16" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": { + "bidder": { + "TagID": "16" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adman/admantest/exemplary/simple-video.json b/adapters/adman/admantest/exemplary/simple-video.json new file mode 100644 index 00000000000..d7fa82d274d --- /dev/null +++ b/adapters/adman/admantest/exemplary/simple-video.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "22" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "tagid": "22", + "ext": { + "bidder": { + "TagID": "22" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adman/admantest/exemplary/simple-web-banner.json b/adapters/adman/admantest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..ce872bff52b --- /dev/null +++ b/adapters/adman/admantest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/adman/admantest/params/banner.json b/adapters/adman/admantest/params/banner.json new file mode 100644 index 00000000000..03fa8f3f2d8 --- /dev/null +++ b/adapters/adman/admantest/params/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "16" +} \ No newline at end of file diff --git a/adapters/adman/admantest/params/race/banner.json b/adapters/adman/admantest/params/race/banner.json new file mode 100644 index 00000000000..03fa8f3f2d8 --- /dev/null +++ b/adapters/adman/admantest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "16" +} \ No newline at end of file diff --git a/adapters/adman/admantest/params/race/video.json b/adapters/adman/admantest/params/race/video.json new file mode 100644 index 00000000000..e776c928a7e --- /dev/null +++ b/adapters/adman/admantest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "22" +} \ No newline at end of file diff --git a/adapters/adman/admantest/params/video.json b/adapters/adman/admantest/params/video.json new file mode 100644 index 00000000000..e776c928a7e --- /dev/null +++ b/adapters/adman/admantest/params/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "22" +} \ No newline at end of file diff --git a/adapters/adman/admantest/supplemental/bad-imp-ext.json b/adapters/adman/admantest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..db3c8de5767 --- /dev/null +++ b/adapters/adman/admantest/supplemental/bad-imp-ext.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": { + "adman": { + "TagID": "16" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, +"expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } +] +} diff --git a/adapters/adman/admantest/supplemental/bad_response.json b/adapters/adman/admantest/supplemental/bad_response.json new file mode 100644 index 00000000000..8c349297e73 --- /dev/null +++ b/adapters/adman/admantest/supplemental/bad_response.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adman/admantest/supplemental/no-imp-ext-1.json b/adapters/adman/admantest/supplemental/no-imp-ext-1.json new file mode 100644 index 00000000000..8fad5ba5ef0 --- /dev/null +++ b/adapters/adman/admantest/supplemental/no-imp-ext-1.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/adman/admantest/supplemental/no-imp-ext-2.json b/adapters/adman/admantest/supplemental/no-imp-ext-2.json new file mode 100644 index 00000000000..337dfd044b3 --- /dev/null +++ b/adapters/adman/admantest/supplemental/no-imp-ext-2.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/adman/admantest/supplemental/status-204.json b/adapters/adman/admantest/supplemental/status-204.json new file mode 100644 index 00000000000..7f9a12dec29 --- /dev/null +++ b/adapters/adman/admantest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }] +} diff --git a/adapters/adman/admantest/supplemental/status-404.json b/adapters/adman/admantest/supplemental/status-404.json new file mode 100644 index 00000000000..560878342f0 --- /dev/null +++ b/adapters/adman/admantest/supplemental/status-404.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adman/params_test.go b/adapters/adman/params_test.go new file mode 100644 index 00000000000..a80c2a44b8b --- /dev/null +++ b/adapters/adman/params_test.go @@ -0,0 +1,46 @@ +package adman + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// TestValidParams makes sure that the adman schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdman, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adman params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adman schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdman, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"TagID": "16"}`, +} + +var invalidParams = []string{ + `{"id": "123"}`, + `{"tagid": "123"}`, + `{"TagID": 16}`, +} diff --git a/adapters/adman/usersync.go b/adapters/adman/usersync.go new file mode 100644 index 00000000000..aae6afcdfcd --- /dev/null +++ b/adapters/adman/usersync.go @@ -0,0 +1,13 @@ +package adman + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +// NewAdmanSyncer returns adman syncer +func NewAdmanSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adman", 149, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/adman/usersync_test.go b/adapters/adman/usersync_test.go new file mode 100644 index 00000000000..55a6e2cec97 --- /dev/null +++ b/adapters/adman/usersync_test.go @@ -0,0 +1,35 @@ +package adman + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdmanSyncer(t *testing.T) { + syncURL := "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdmanSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "ANDFJDS", + }, + CCPA: ccpa.Policy{ + Value: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://sync.admanmedia.com/pbs.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 149, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index c50118e2008..bb2c3191491 100755 --- a/config/config.go +++ b/config/config.go @@ -563,6 +563,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtarget, "https://sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") @@ -763,6 +764,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}") + v.SetDefault("adapters.adman.endpoint", "http://eu-ams-1.admanmedia.com/?c=o&m=ortb") v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 44054df06fd..c30bb0c622e 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -14,6 +14,7 @@ import ( "github.com/prebid/prebid-server/adapters/adhese" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" + "github.com/prebid/prebid-server/adapters/adman" "github.com/prebid/prebid-server/adapters/admixer" "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" @@ -98,6 +99,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdhese: adhese.NewAdheseBidder(cfg.Adapters[string(openrtb_ext.BidderAdhese)].Endpoint), openrtb_ext.BidderAdkernel: adkernel.NewAdkernelAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernel))].Endpoint), openrtb_ext.BidderAdkernelAdn: adkernelAdn.NewAdkernelAdnAdapter(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernelAdn))].Endpoint), + openrtb_ext.BidderAdman: adman.NewAdmanBidder(cfg.Adapters[string(openrtb_ext.BidderAdman)].Endpoint), openrtb_ext.BidderAdmixer: admixer.NewAdmixerBidder(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdmixer))].Endpoint), openrtb_ext.BidderAdOcean: adocean.NewAdOceanBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdOcean))].Endpoint), openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1f9cffb9938..49d7b09d671 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -31,6 +31,7 @@ const ( BidderAdkernel BidderName = "adkernel" BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdpone BidderName = "adpone" + BidderAdman BidderName = "adman" BidderAdmixer BidderName = "admixer" BidderAdOcean BidderName = "adocean" BidderAdtarget BidderName = "adtarget" @@ -110,6 +111,7 @@ var BidderMap = map[string]BidderName{ "adhese": BidderAdhese, "adkernel": BidderAdkernel, "adkernelAdn": BidderAdkernelAdn, + "adman": BidderAdman, "admixer": BidderAdmixer, "adocean": BidderAdOcean, "adpone": BidderAdpone, diff --git a/openrtb_ext/imp_adman.go b/openrtb_ext/imp_adman.go new file mode 100644 index 00000000000..bc79415452c --- /dev/null +++ b/openrtb_ext/imp_adman.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpAdman defines adman specifiec param +type ExtImpAdman struct { + TagID string `json:"TagID"` +} diff --git a/static/bidder-info/adman.yaml b/static/bidder-info/adman.yaml new file mode 100644 index 00000000000..932ef2e4242 --- /dev/null +++ b/static/bidder-info/adman.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "prebid@admanmedia.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/adman.json b/static/bidder-params/adman.json new file mode 100644 index 00000000000..90021e2cdfd --- /dev/null +++ b/static/bidder-params/adman.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adman Adapter Params", + "description": "A schema which validates params accepted by the Adman adapter", + + "type": "object", + "properties": { + "TagID": { + "type": "string", + "description": "An ID which identifies the adman ad tag" + } + }, + "required" : [ "TagID" ] + } + \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 5657c8b7010..f1f643afb74 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" + "github.com/prebid/prebid-server/adapters/adman" "github.com/prebid/prebid-server/adapters/admixer" "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adpone" @@ -84,6 +85,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdman, adman.NewAdmanSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdmixer, admixer.NewAdmixerSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdOcean, adocean.NewAdOceanSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdpone, adpone.NewadponeSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 363cd491648..b23541eaf8a 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -18,6 +18,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdform): syncConfig, string(openrtb_ext.BidderAdkernel): syncConfig, string(openrtb_ext.BidderAdkernelAdn): syncConfig, + string(openrtb_ext.BidderAdman): syncConfig, string(openrtb_ext.BidderAdmixer): syncConfig, string(openrtb_ext.BidderAdOcean): syncConfig, string(openrtb_ext.BidderAdpone): syncConfig, From e376a8bbfcf513d65821f9c97547913d9a9c0d93 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 24 Jun 2020 14:08:14 -0400 Subject: [PATCH 125/603] =?UTF-8?q?PBS-632=20add=20max=20connections=20per?= =?UTF-8?q?=20host=20config=20setting=20to=20general=20http=20a=E2=80=A6?= =?UTF-8?q?=20(#1366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 3 +++ config/config_test.go | 4 ++++ router/router.go | 2 ++ 3 files changed, 9 insertions(+) diff --git a/config/config.go b/config/config.go index bb2c3191491..7d34954583f 100755 --- a/config/config.go +++ b/config/config.go @@ -74,6 +74,7 @@ type Configuration struct { const MIN_COOKIE_SIZE_BYTES = 500 type HTTPClient struct { + MaxConnsPerHost int `mapstructure:"max_connections_per_host"` MaxIdleConns int `mapstructure:"max_idle_connections"` MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"` IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` @@ -669,9 +670,11 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("host_cookie.value", "") v.SetDefault("host_cookie.ttl_days", 90) v.SetDefault("host_cookie.max_cookie_size_bytes", 0) + v.SetDefault("http_client.max_connections_per_host", 0) // unlimited v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client_cache.max_connections_per_host", 0) // unlimited v.SetDefault("http_client_cache.max_idle_connections", 10) v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) diff --git a/config/config_test.go b/config/config_test.go index 2b291fe978d..c7d406cc8cc 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -67,10 +67,12 @@ external_cache: host: www.externalprebidcache.net path: endpoints/cache http_client: + max_connections_per_host: 10 max_idle_connections: 500 max_idle_connections_per_host: 20 idle_connection_timeout_seconds: 30 http_client_cache: + max_connections_per_host: 5 max_idle_connections: 1 max_idle_connections_per_host: 2 idle_connection_timeout_seconds: 3 @@ -217,9 +219,11 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%") cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net") cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "endpoints/cache") + cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10) cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500) cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20) cmpInts(t, "http_client.idle_connection_timeout_seconds", cfg.Client.IdleConnTimeout, 30) + cmpInts(t, "http_client_cache.max_connections_per_host", cfg.CacheClient.MaxConnsPerHost, 5) cmpInts(t, "http_client_cache.max_idle_connections", cfg.CacheClient.MaxIdleConns, 1) cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) diff --git a/router/router.go b/router/router.go index 045c86ef25f..30936705a22 100644 --- a/router/router.go +++ b/router/router.go @@ -188,6 +188,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r generalHttpClient := &http.Client{ Transport: &http.Transport{ + MaxConnsPerHost: cfg.Client.MaxConnsPerHost, MaxIdleConns: cfg.Client.MaxIdleConns, MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, @@ -197,6 +198,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r cacheHttpClient := &http.Client{ Transport: &http.Transport{ + MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, MaxIdleConns: cfg.CacheClient.MaxIdleConns, MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second, From 16676360160b9a53286e8c8bcacf3454d923457d Mon Sep 17 00:00:00 2001 From: Marsel Date: Thu, 25 Jun 2020 17:29:25 +0300 Subject: [PATCH 126/603] Add ext.bidder.zoneid for Kubient adapater (#1367) * Add ext.bidder.zoneid for Kubient adapater * Check the number of Imps. zoneid is optional. --- adapters/kubient/kubient.go | 49 ++++++++++++++++--- .../kubient/kubienttest/exemplary/banner.json | 8 ++- .../kubient/kubienttest/exemplary/video.json | 8 ++- .../supplemental/bad_response.json | 8 ++- .../supplemental/missing-zoneid.json | 31 ++++++++++++ .../kubienttest/supplemental/no-imps.json | 12 +++++ .../kubienttest/supplemental/status_204.json | 2 + .../kubienttest/supplemental/status_400.json | 8 ++- openrtb_ext/imp_kubient.go | 6 +++ static/bidder-params/kubient.json | 8 ++- 10 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 adapters/kubient/kubienttest/supplemental/missing-zoneid.json create mode 100644 adapters/kubient/kubienttest/supplemental/no-imps.json create mode 100644 openrtb_ext/imp_kubient.go diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go index cb1fe93ff82..acfaa44b6af 100644 --- a/adapters/kubient/kubient.go +++ b/adapters/kubient/kubient.go @@ -24,10 +24,24 @@ type KubientAdapter struct { func (adapter *KubientAdapter) MakeRequests( openRTBRequest *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo, -) ( - requestsToBidder []*adapters.RequestData, - errs []error, -) { +) ([]*adapters.RequestData, []error) { + if len(openRTBRequest.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + errs := make([]error, 0, len(openRTBRequest.Imp)) + hasErrors := false + for _, impObj := range openRTBRequest.Imp { + err := checkImpExt(impObj) + if err != nil { + errs = append(errs, err) + hasErrors = true + } + } + if hasErrors { + return nil, errs + } openRTBRequestJSON, err := json.Marshal(openRTBRequest) if err != nil { errs = append(errs, err) @@ -36,17 +50,36 @@ func (adapter *KubientAdapter) MakeRequests( headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") - requestToBidder := &adapters.RequestData{ + requestsToBidder := []*adapters.RequestData{{ Method: "POST", Uri: adapter.endpoint, Body: openRTBRequestJSON, Headers: headers, - } - requestsToBidder = append(requestsToBidder, requestToBidder) - + }} return requestsToBidder, errs } +func checkImpExt(impObj openrtb.Imp) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(impObj.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + var kubientExt openrtb_ext.ExtImpKubient + if err := json.Unmarshal(bidderExt.Bidder, &kubientExt); err != nil { + return &errortypes.BadInput{ + Message: "ext.bidder.zoneid is not provided", + } + } + if kubientExt.ZoneID == "" { + return &errortypes.BadInput{ + Message: "zoneid is empty", + } + } + return nil +} + // MakeBids makes the bids func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error diff --git a/adapters/kubient/kubienttest/exemplary/banner.json b/adapters/kubient/kubienttest/exemplary/banner.json index a32c761a7d0..9af4f9f8cfa 100644 --- a/adapters/kubient/kubienttest/exemplary/banner.json +++ b/adapters/kubient/kubienttest/exemplary/banner.json @@ -17,7 +17,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "9042" + } } } ] @@ -44,7 +46,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "9042" + } } } ] diff --git a/adapters/kubient/kubienttest/exemplary/video.json b/adapters/kubient/kubienttest/exemplary/video.json index 59d32874cec..d9346c3fa46 100644 --- a/adapters/kubient/kubienttest/exemplary/video.json +++ b/adapters/kubient/kubienttest/exemplary/video.json @@ -11,7 +11,9 @@ "h": 576 }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "9010" + } } } ] @@ -32,7 +34,9 @@ "h": 576 }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "9010" + } } } ] diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/kubient/kubienttest/supplemental/bad_response.json index 166743cf497..076acf29058 100644 --- a/adapters/kubient/kubienttest/supplemental/bad_response.json +++ b/adapters/kubient/kubienttest/supplemental/bad_response.json @@ -13,7 +13,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "23" + } } } ] @@ -36,7 +38,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "23" + } } } ] diff --git a/adapters/kubient/kubienttest/supplemental/missing-zoneid.json b/adapters/kubient/kubienttest/supplemental/missing-zoneid.json new file mode 100644 index 00000000000..cfd616621e2 --- /dev/null +++ b/adapters/kubient/kubienttest/supplemental/missing-zoneid.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-missing-req-param-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "zoneid is empty", + "comparison": "literal" + } + ] +} diff --git a/adapters/kubient/kubienttest/supplemental/no-imps.json b/adapters/kubient/kubienttest/supplemental/no-imps.json new file mode 100644 index 00000000000..189adf9a932 --- /dev/null +++ b/adapters/kubient/kubienttest/supplemental/no-imps.json @@ -0,0 +1,12 @@ +{ + "mockBidRequest": { + "id": "test-no-imp-request-id", + "imp": [] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kubient/kubienttest/supplemental/status_204.json b/adapters/kubient/kubienttest/supplemental/status_204.json index 58bb2629a5e..6794d58be6c 100644 --- a/adapters/kubient/kubienttest/supplemental/status_204.json +++ b/adapters/kubient/kubienttest/supplemental/status_204.json @@ -14,6 +14,7 @@ }, "ext": { "bidder": { + "zoneid": "203" } } } @@ -39,6 +40,7 @@ }, "ext": { "bidder": { + "zoneid": "203" } } } diff --git a/adapters/kubient/kubienttest/supplemental/status_400.json b/adapters/kubient/kubienttest/supplemental/status_400.json index e895f793dc1..29438cc3b8b 100644 --- a/adapters/kubient/kubienttest/supplemental/status_400.json +++ b/adapters/kubient/kubienttest/supplemental/status_400.json @@ -13,7 +13,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "102" + } } } ] @@ -37,7 +39,9 @@ ] }, "ext": { - "bidder": {} + "bidder": { + "zoneid": "102" + } } } ] diff --git a/openrtb_ext/imp_kubient.go b/openrtb_ext/imp_kubient.go new file mode 100644 index 00000000000..fafd2a0eb8f --- /dev/null +++ b/openrtb_ext/imp_kubient.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpKubient defines the contract for bidrequest.imp[i].ext.kubient +type ExtImpKubient struct { + ZoneID string `json:"zoneid"` +} diff --git a/static/bidder-params/kubient.json b/static/bidder-params/kubient.json index a75dd734ff2..9b975289a7b 100644 --- a/static/bidder-params/kubient.json +++ b/static/bidder-params/kubient.json @@ -3,5 +3,11 @@ "title": "Kubient Adapter Params", "description": "A schema which validates params accepted by the Kubient adapter", "type": "object", - "properties": { } + "properties": { + "zoneid": { + "type": "string", + "description": "Zone ID identifies Kubient placement ID.", + "minLength": 1 + } + } } From 8378a4529e66dc389167984607b8b4adf2a31dbf Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 29 Jun 2020 14:21:20 -0400 Subject: [PATCH 127/603] Improved IPv6 Support + Private Network Filtering (#1362) --- config/config.go | 41 ++- config/config_test.go | 74 +++- config/requestvalidation.go | 55 +++ config/requestvalidation_test.go | 145 ++++++++ endpoints/openrtb2/amp_auction.go | 9 +- endpoints/openrtb2/auction.go | 96 +++-- endpoints/openrtb2/auction_test.go | 196 ++++++++++- .../supplementary/site-has-ipv4.json | 38 ++ .../supplementary/site-has-ipv6.json | 38 ++ endpoints/openrtb2/video_auction.go | 24 +- endpoints/openrtb2/video_auction_test.go | 10 +- exchange/exchange_test.go | 15 +- main_test.go | 14 +- pbs/pbsrequest.go | 11 +- prebid/prebid.go | 82 ----- util/httputil/httputil.go | 99 ++++++ util/httputil/httputil_test.go | 327 ++++++++++++++++++ util/iputil/parse.go | 27 ++ util/iputil/parse_test.go | 30 ++ util/iputil/validator.go | 48 +++ util/iputil/validator_test.go | 222 ++++++++++++ 21 files changed, 1420 insertions(+), 181 deletions(-) create mode 100644 config/requestvalidation.go create mode 100644 config/requestvalidation_test.go create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv4.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv6.json delete mode 100644 prebid/prebid.go create mode 100644 util/httputil/httputil.go create mode 100644 util/httputil/httputil_test.go create mode 100644 util/iputil/parse.go create mode 100644 util/iputil/parse_test.go create mode 100644 util/iputil/validator.go create mode 100644 util/iputil/validator_test.go diff --git a/config/config.go b/config/config.go index 7d34954583f..50cfbb1c170 100755 --- a/config/config.go +++ b/config/config.go @@ -17,7 +17,7 @@ import ( validator "github.com/asaskevich/govalidator" ) -// Configuration +// Configuration specifies the static application config. type Configuration struct { ExternalURL string `mapstructure:"external_url"` Host string `mapstructure:"host"` @@ -69,6 +69,8 @@ type Configuration struct { RequestTimeoutHeaders RequestTimeoutHeaders `mapstructure:"request_timeout_headers"` // Debug/logging flags go here Debug Debug `mapstructure:"debug"` + // RequestValidation specifies the request validation options. + RequestValidation RequestValidation `mapstructure:"request_validation"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -239,15 +241,15 @@ type HostCookie struct { TTL int64 `mapstructure:"ttl_days"` } +func (cfg *HostCookie) TTLDuration() time.Duration { + return time.Duration(cfg.TTL) * time.Hour * 24 +} + type RequestTimeoutHeaders struct { RequestTimeInQueue string `mapstructure:"request_time_in_queue"` RequestTimeoutInQueue string `mapstructure:"request_timeout_in_queue"` } -func (cfg *HostCookie) TTLDuration() time.Duration { - return time.Duration(cfg.TTL) * time.Hour * 24 -} - const ( dummyHost string = "dummyhost.com" dummyPublisherID string = "12" @@ -498,6 +500,15 @@ func New(v *viper.Viper) (*Configuration, error) { } c.setDerivedDefaults() + if err := c.RequestValidation.Parse(); err != nil { + return nil, err + } + + if err := isValidCookieSize(c.HostCookie.MaxCookieSizeBytes); err != nil { + glog.Fatal(fmt.Printf("Max cookie size %d cannot be less than %d \n", c.HostCookie.MaxCookieSizeBytes, MIN_COOKIE_SIZE_BYTES)) + return nil, err + } + // To look for a request's publisher_id in the NonStandardPublishers list in // O(1) time, we fill this hash table located in the NonStandardPublisherMap field of GDPR c.GDPR.NonStandardPublisherMap = make(map[string]int) @@ -519,11 +530,6 @@ func New(v *viper.Viper) (*Configuration, error) { c.BlacklistedAcctMap[c.BlacklistedAccts[i]] = true } - if err := isValidCookieSize(c.HostCookie.MaxCookieSizeBytes); err != nil { - glog.Fatal(fmt.Printf("Max cookie size %d cannot be less than %d \n", c.HostCookie.MaxCookieSizeBytes, MIN_COOKIE_SIZE_BYTES)) - return nil, err - } - glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") if errs := c.validate(); len(errs) > 0 { @@ -875,8 +881,23 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("debug.timeout_notification.sampling_rate", 0.0) v.SetDefault("debug.timeout_notification.fail_only", false) + /* IPv4 + /* Site Local: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + /* Link Local: 169.254.0.0/16 + /* Loopback: 127.0.0.0/8 + /* + /* IPv6 + /* Loopback: ::1/128 + /* Unique Local: fc00::/7 + /* Link Local: fe80::/10 + /* Multicast: ff00::/8 + */ + v.SetDefault("request_validation.ipv4_private_networks", []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}) + v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8"}) + // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.SetTypeByDefaultValue(true) v.SetEnvPrefix("PBS") v.AutomaticEnv() v.ReadInConfig() diff --git a/config/config_test.go b/config/config_test.go index c7d406cc8cc..3456694db5c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,7 +2,7 @@ package config import ( "bytes" - "fmt" + "net" "strings" "testing" "time" @@ -119,6 +119,9 @@ adapters: blacklisted_apps: ["spamAppID","sketchy-app-id"] account_required: true certificates_file: /etc/ssl/cert.pem +request_validation: + ipv4_private_networks: ["1.1.1.0/24"] + ipv6_private_networks: ["1111::/16", "2222::/16"] `) var adapterExtraInfoConfig = []byte(` @@ -292,6 +295,9 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_required", cfg.AccountRequired, true) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") + cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") + cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") + cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16") } func TestUnmarshalAdapterExtraInfo(t *testing.T) { @@ -412,23 +418,63 @@ func TestLimitTimeout(t *testing.T) { } func TestCookieSizeError(t *testing.T) { - type aTest struct { - cookieHost *HostCookie + testCases := []struct { + description string + cookieSize int expectError bool + }{ + {"MIN_COOKIE_SIZE_BYTES + 1", MIN_COOKIE_SIZE_BYTES + 1, false}, + {"MIN_COOKIE_SIZE_BYTES", MIN_COOKIE_SIZE_BYTES, false}, + {"MIN_COOKIE_SIZE_BYTES - 1", MIN_COOKIE_SIZE_BYTES - 1, true}, + {"Zero", 0, false}, + {"Negative", -100, true}, } - testCases := []aTest{ - {cookieHost: &HostCookie{MaxCookieSizeBytes: 1 << 15}, expectError: false}, //32 KB, no error - {cookieHost: &HostCookie{MaxCookieSizeBytes: 800}, expectError: false}, - {cookieHost: &HostCookie{MaxCookieSizeBytes: 500}, expectError: false}, - {cookieHost: &HostCookie{MaxCookieSizeBytes: 0}, expectError: false}, - {cookieHost: &HostCookie{MaxCookieSizeBytes: 200}, expectError: true}, - {cookieHost: &HostCookie{MaxCookieSizeBytes: -100}, expectError: true}, + + for _, test := range testCases { + resultErr := isValidCookieSize(test.cookieSize) + + if test.expectError { + assert.Error(t, resultErr, test.description) + } else { + assert.NoError(t, resultErr, test.description) + } + } +} + +func TestNewCallsRequestValidation(t *testing.T) { + testCases := []struct { + description string + privateIPNetworks string + expectedError string + expectedIPs []net.IPNet + }{ + { + description: "Valid", + privateIPNetworks: `["1.1.1.0/24"]`, + expectedIPs: []net.IPNet{{IP: net.IP{1, 1, 1, 0}, Mask: net.CIDRMask(24, 32)}}, + }, + { + description: "Invalid", + privateIPNetworks: `["1"]`, + expectedError: "Invalid private IPv4 networks: '1'", + }, } - for i := range testCases { - if testCases[i].expectError { - assert.Error(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes less than MIN_COOKIE_SIZE_BYTES = %d and not equal to zero should return an error", MIN_COOKIE_SIZE_BYTES)) + + for _, test := range testCases { + v := viper.New() + SetupViper(v, "") + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer([]byte( + `request_validation: + ipv4_private_networks: ` + test.privateIPNetworks))) + + result, resultErr := New(v) + + if test.expectedError == "" { + assert.NoError(t, resultErr, test.description+":err") + assert.ElementsMatch(t, test.expectedIPs, result.RequestValidation.IPv4PrivateNetworksParsed, test.description+":parsed") } else { - assert.NoError(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes greater than MIN_COOKIE_SIZE_BYTES = %d or equal to zero should not return an error", MIN_COOKIE_SIZE_BYTES)) + assert.Error(t, resultErr, test.description+":err") } } } diff --git a/config/requestvalidation.go b/config/requestvalidation.go new file mode 100644 index 00000000000..0824f4da880 --- /dev/null +++ b/config/requestvalidation.go @@ -0,0 +1,55 @@ +package config + +import ( + "errors" + "fmt" + "net" + "strings" +) + +// RequestValidation specifies the request validation options. +type RequestValidation struct { + IPv4PrivateNetworks []string `mapstructure:"ipv4_private_networks,flow"` + IPv4PrivateNetworksParsed []net.IPNet + + IPv6PrivateNetworks []string `mapstructure:"ipv6_private_networks,flow"` + IPv6PrivateNetworksParsed []net.IPNet +} + +// Parse converts the CIDR representation of the IPv4 and IPv6 private networks as net.IPNet structs, or returns an error if at least one is invalid. +func (r *RequestValidation) Parse() error { + ipv4Nets, err := parseNetworks(r.IPv4PrivateNetworks, net.IPv4len) + if err != nil { + return errors.New("Invalid private IPv4 network: " + err.Error()) + } + + ipv6Nets, err := parseNetworks(r.IPv6PrivateNetworks, net.IPv6len) + if err != nil { + return errors.New("Invalid private IPv6 network: " + err.Error()) + } + + r.IPv4PrivateNetworksParsed = ipv4Nets + r.IPv6PrivateNetworksParsed = ipv6Nets + return nil +} + +func parseNetworks(networks []string, networksLen int) ([]net.IPNet, error) { + ipNetworks := make([]net.IPNet, 0, len(networks)) + errMsg := strings.Builder{} + + for _, v := range networks { + v := strings.TrimSpace(v) + + if _, ipNet, err := net.ParseCIDR(v); err != nil || len(ipNet.IP) != networksLen { + fmt.Fprintf(&errMsg, "'%s',", v) + } else { + ipNetworks = append(ipNetworks, *ipNet) + } + } + + if errMsg.Len() > 0 { + return nil, errors.New(errMsg.String()[:errMsg.Len()-1]) + } + + return ipNetworks, nil +} diff --git a/config/requestvalidation_test.go b/config/requestvalidation_test.go new file mode 100644 index 00000000000..cacb4f2d140 --- /dev/null +++ b/config/requestvalidation_test.go @@ -0,0 +1,145 @@ +package config + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + ipv4Mask16 := net.CIDRMask(16, 32) + ipv4Mask24 := net.CIDRMask(24, 32) + + ipv6Mask16 := net.CIDRMask(16, 128) + ipv6Mask32 := net.CIDRMask(32, 128) + + testCases := []struct { + description string + ipv4 []string + ipv4Expected []net.IPNet + ipv6 []string + ipv6Expected []net.IPNet + expectedErr string + }{ + { + description: "Empty", + ipv4: []string{}, + ipv4Expected: []net.IPNet{}, + ipv6: []string{}, + ipv6Expected: []net.IPNet{}, + }, + { + description: "One", + ipv4: []string{"1.1.1.1/24"}, + ipv4Expected: []net.IPNet{{IP: net.IP{1, 1, 1, 0}, Mask: ipv4Mask24}}, + ipv6: []string{"1111:2222::/16"}, + ipv6Expected: []net.IPNet{{IP: net.ParseIP("1111::"), Mask: ipv6Mask16}}, + }, + { + description: "One - Ignore Whitespace", + ipv4: []string{" 1.1.1.1/24 "}, + ipv4Expected: []net.IPNet{{IP: net.IP{1, 1, 1, 0}, Mask: ipv4Mask24}}, + ipv6: []string{" 1111:2222::/16 "}, + ipv6Expected: []net.IPNet{{IP: net.ParseIP("1111::"), Mask: ipv6Mask16}}, + }, + { + description: "Many", + ipv4: []string{"1.1.1.1/24", "2.2.2.2/16"}, + ipv4Expected: []net.IPNet{{IP: net.IP{1, 1, 1, 0}, Mask: ipv4Mask24}, {IP: net.IP{2, 2, 0, 0}, Mask: ipv4Mask16}}, + ipv6: []string{"1111:2222::/16", "1111:2222:3333::/32"}, + ipv6Expected: []net.IPNet{{IP: net.ParseIP("1111::"), Mask: ipv6Mask16}, {IP: net.ParseIP("1111:2222::"), Mask: ipv6Mask32}}, + }, + { + description: "Malformed - IPv4 - One", + ipv4: []string{"malformed1"}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: 'malformed1'", + }, + { + description: "Malformed - IPv4 - Many", + ipv4: []string{"malformed1", "malformed2"}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: 'malformed1','malformed2'", + }, + { + description: "Malformed - IPv6 - One", + ipv4: []string{}, + ipv6: []string{"malformed2"}, + expectedErr: "Invalid private IPv6 network: 'malformed2'", + }, + { + description: "Malformed - IPv6 - Many", + ipv4: []string{}, + ipv6: []string{"malformed1", "malformed2"}, + expectedErr: "Invalid private IPv6 network: 'malformed1','malformed2'", + }, + { + description: "Malformed - Mixed", + ipv4: []string{"malformed1"}, + ipv6: []string{"malformed2"}, + expectedErr: "Invalid private IPv4 network: 'malformed1'", + }, + { + description: "Malformed - IPv4 - Ignore Whitespace", + ipv4: []string{" malformed1 "}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: 'malformed1'", + }, + { + description: "Malformed - IPv6 - Ignore Whitespace", + ipv4: []string{}, + ipv6: []string{" malformed2 "}, + expectedErr: "Invalid private IPv6 network: 'malformed2'", + }, + { + description: "Malformed - IPv4 - Missing Network Mask", + ipv4: []string{"1.1.1.1"}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: '1.1.1.1'", + }, + { + description: "Malformed - IPv6 - Missing Network Mask", + ipv4: []string{}, + ipv6: []string{"1111::"}, + expectedErr: "Invalid private IPv6 network: '1111::'", + }, + { + description: "Malformed - IPv4 - Wrong IP Version", + ipv4: []string{"1111::/16"}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: '1111::/16'", + }, + { + description: "Malformed - IPv6 - Wrong IP Version", + ipv4: []string{}, + ipv6: []string{"1.1.1.1/16"}, + expectedErr: "Invalid private IPv6 network: '1.1.1.1/16'", + }, + { + description: "Malformed - IPv6 Mapped IPv4", + ipv4: []string{"::FFFF:1.1.1.1"}, + ipv6: []string{}, + expectedErr: "Invalid private IPv4 network: '::FFFF:1.1.1.1'", + }, + } + + for _, test := range testCases { + requestValidation := &RequestValidation{ + IPv4PrivateNetworks: test.ipv4, + IPv6PrivateNetworks: test.ipv6, + } + + err := requestValidation.Parse() + + if test.expectedErr == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.Error(t, err, test.description+":err") + assert.Equal(t, test.expectedErr, err.Error(), test.description+":err_msg") + } + + assert.ElementsMatch(t, requestValidation.IPv4PrivateNetworksParsed, test.ipv4Expected, test.description+":ipv4") + assert.ElementsMatch(t, requestValidation.IPv6PrivateNetworksParsed, test.ipv6Expected, test.description+":ipv6") + } +} diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 2dcd572c63c..e8b5d3ecc76 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/iputil" ) const defaultAmpRequestTimeoutMillis = 900 @@ -58,6 +59,11 @@ func NewAmpEndpoint( defRequest := defReqJSON != nil && len(defReqJSON) > 0 + ipValidator := iputil.PublicNetworkIPValidator{ + IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, + IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, + } + return httprouter.Handle((&endpointDeps{ ex, validator, @@ -72,7 +78,8 @@ func NewAmpEndpoint( defReqJSON, bidderMap, nil, - nil}).AmpAuction), nil + nil, + ipValidator}).AmpAuction), nil } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bd50fca9149..20acc2aedd3 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -27,12 +27,13 @@ import ( "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" - "github.com/prebid/prebid-server/prebid" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/util/iputil" "golang.org/x/net/publicsuffix" ) @@ -43,8 +44,14 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { return nil, errors.New("NewEndpoint requires non-nil arguments.") } + defRequest := defReqJSON != nil && len(defReqJSON) > 0 + ipValidator := iputil.PublicNetworkIPValidator{ + IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, + IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, + } + return httprouter.Handle((&endpointDeps{ ex, validator, @@ -59,24 +66,26 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato defReqJSON, bidderMap, nil, - nil}).Auction), nil + nil, + ipValidator}).Auction), nil } type endpointDeps struct { - ex exchange.Exchange - paramsValidator openrtb_ext.BidderParamValidator - storedReqFetcher stored_requests.Fetcher - videoFetcher stored_requests.Fetcher - categories stored_requests.CategoryFetcher - cfg *config.Configuration - metricsEngine pbsmetrics.MetricsEngine - analytics analytics.PBSAnalyticsModule - disabledBidders map[string]string - defaultRequest bool - defReqJSON []byte - bidderMap map[string]openrtb_ext.BidderName - cache prebid_cache_client.Client - debugLogRegexp *regexp.Regexp + ex exchange.Exchange + paramsValidator openrtb_ext.BidderParamValidator + storedReqFetcher stored_requests.Fetcher + videoFetcher stored_requests.Fetcher + categories stored_requests.CategoryFetcher + cfg *config.Configuration + metricsEngine pbsmetrics.MetricsEngine + analytics analytics.PBSAnalyticsModule + disabledBidders map[string]string + defaultRequest bool + defReqJSON []byte + bidderMap map[string]openrtb_ext.BidderName + cache prebid_cache_client.Client + debugLogRegexp *regexp.Regexp + privateNetworkIPValidator iputil.IPValidator } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -308,17 +317,14 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return errL } - ccpaPolicy, ccpaPolicyErr := ccpa.ReadPolicy(req) - if ccpaPolicyErr != nil { - errL = append(errL, ccpaPolicyErr) + if policy, err := ccpa.ReadPolicy(req); err != nil { + errL = append(errL, errL...) return errL - } - - if err := ccpaPolicy.Validate(); err != nil { + } else if err := policy.Validate(); err != nil { errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) - ccpaPolicy.Value = "" - if err := ccpaPolicy.Write(req); err != nil { + policy.Value = "" + if err := policy.Write(req); err != nil { errL = append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) } } @@ -914,13 +920,27 @@ func validateRegs(regs *openrtb.Regs) error { return nil } +func sanitizeRequest(r *openrtb.BidRequest, ipValidator iputil.IPValidator) { + if r.Device != nil { + if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { + r.Device.IP = "" + } + + if ip, ver := iputil.ParseIP(r.Device.IPv6); ip == nil || ver != iputil.IPv6 || !ipValidator.IsValid(ip, ver) { + r.Device.IPv6 = "" + } + } +} + // setFieldsImplicitly uses _implicit_ information from the httpReq to set values on bidReq. // This function does not consume the request body, which was set explicitly, but infers certain // OpenRTB properties from the headers and other implicit info. // // This function _should not_ override any fields which were defined explicitly by the caller in the request. func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { - setDeviceImplicitly(httpReq, bidReq) + sanitizeRequest(bidReq, deps.privateNetworkIPValidator) + + setDeviceImplicitly(httpReq, bidReq, deps.privateNetworkIPValidator) // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. if bidReq.App == nil { @@ -932,8 +952,8 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope } // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device -func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { - setIPImplicitly(httpReq, bidReq) // Fixes #230 +func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) { + setIPImplicitly(httpReq, bidReq, ipValidtor) setUAImplicitly(httpReq, bidReq) } @@ -975,7 +995,7 @@ func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { func setImpsImplicitly(httpReq *http.Request, imps []openrtb.Imp) { secure := int8(1) for i := 0; i < len(imps); i++ { - if imps[i].Secure == nil && prebid.IsSecure(httpReq) { + if imps[i].Secure == nil && httputil.IsSecure(httpReq) { imps[i].Secure = &secure } } @@ -1132,13 +1152,21 @@ func getStoredRequestId(data []byte) (string, bool, error) { } // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. -func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { - if bidReq.Device == nil || bidReq.Device.IP == "" { - if ip := prebid.GetIP(httpReq); ip != "" { - if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} +func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidator iputil.IPValidator) { + if bidReq.Device == nil || (bidReq.Device.IP == "" && bidReq.Device.IPv6 == "") { + if ip, ver := httputil.FindIP(httpReq, ipValidator); ip != nil { + switch ver { + case iputil.IPv4: + if bidReq.Device == nil { + bidReq.Device = &openrtb.Device{} + } + bidReq.Device.IP = ip.String() + case iputil.IPv6: + if bidReq.Device == nil { + bidReq.Device = &openrtb.Device{} + } + bidReq.Device.IPv6 = ip.String() } - bidReq.Device.IP = ip } } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index c3b9267bf8b..97f0038a392 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "net" "net/http" "net/http/httptest" "os" @@ -29,6 +30,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/util/iputil" "github.com/stretchr/testify/assert" ) @@ -526,26 +528,79 @@ func TestAuctionTypeDefault(t *testing.T) { } } -// TestImplicitIPs prevents #230 -func TestImplicitIPs(t *testing.T) { - ex := &nobidExchange{} - // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. - // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - endpoint, _ := NewEndpoint(ex, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) +func TestImplicitIPsEndToEnd(t *testing.T) { + testCases := []struct { + description string + reqJSONFile string + xForwardedForHeader string + privateNetworksIPv4 []net.IPNet + privateNetworksIPv6 []net.IPNet + expectedDeviceIPv4 string + expectedDeviceIPv6 string + }{ + { + description: "IPv4", + reqJSONFile: "site.json", + xForwardedForHeader: "1.1.1.1", + expectedDeviceIPv4: "1.1.1.1", + }, + { + description: "IPv6", + reqJSONFile: "site.json", + xForwardedForHeader: "1111::", + expectedDeviceIPv6: "1111::", + }, + { + description: "IPv4 - Defined In Request", + reqJSONFile: "site-has-ipv4.json", + xForwardedForHeader: "1.1.1.1", + expectedDeviceIPv4: "8.8.8.8", // Hardcoded value in test file. + }, + { + description: "IPv6 - Defined In Request", + reqJSONFile: "site-has-ipv6.json", + xForwardedForHeader: "1111::", + expectedDeviceIPv6: "8888::", // Hardcoded value in test file. + }, + { + description: "IPv4 - Defined In Request - Private Network", + reqJSONFile: "site-has-ipv4.json", + xForwardedForHeader: "1.1.1.1", + privateNetworksIPv4: []net.IPNet{{IP: net.IP{8, 8, 8, 0}, Mask: net.CIDRMask(24, 32)}}, // Hardcoded value in test file. + expectedDeviceIPv4: "1.1.1.1", + }, + { + description: "IPv6 - Defined In Request - Private Network", + reqJSONFile: "site-has-ipv6.json", + xForwardedForHeader: "1111::", + privateNetworksIPv6: []net.IPNet{{IP: net.ParseIP("8800::"), Mask: net.CIDRMask(8, 128)}}, // Hardcoded value in test file. + expectedDeviceIPv6: "1111::", + }, + } - httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) - httpReq.Header.Set("X-Forwarded-For", "123.456.78.90") - recorder := httptest.NewRecorder() + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + for _, test := range testCases { + exchange := &nobidExchange{} + cfg := &config.Configuration{ + MaxRequestSize: maxSize, + RequestValidation: config.RequestValidation{ + IPv4PrivateNetworksParsed: test.privateNetworksIPv4, + IPv6PrivateNetworksParsed: test.privateNetworksIPv6, + }, + } + endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) - endpoint(recorder, httpReq, nil) + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) + httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader) - if ex.gotRequest == nil { - t.Fatalf("The request never made it into the Exchange.") - } + endpoint(httptest.NewRecorder(), httpReq, nil) - if ex.gotRequest.Device.IP != "123.456.78.90" { - t.Errorf("Bad device IP. Expected 123.456.78.90, got %s", ex.gotRequest.Device.IP) + result := exchange.gotRequest + if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") { + t.FailNow() + } + assert.Equal(t, test.expectedDeviceIPv4, result.Device.IP, test.description+":ipv4") + assert.Equal(t, test.expectedDeviceIPv6, result.Device.IPv6, test.description+":ipv6") } } @@ -602,10 +657,26 @@ func TestStoredRequests(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap, nil, nil} + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + theMetrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } for i, requestData := range testStoredRequests { - newRequest, errList := edep.processStoredRequests(context.Background(), json.RawMessage(requestData)) + newRequest, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) if len(errList) != 0 { for _, err := range errList { if err != nil { @@ -640,6 +711,7 @@ func TestOversizedRequest(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -674,6 +746,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -813,6 +886,7 @@ func TestDisabledBidder(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -848,6 +922,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } errs := deps.validateImpExt(imp, nil, 0) assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext)) @@ -888,6 +963,7 @@ func TestCurrencyTrunc(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } ui := uint64(1) @@ -931,6 +1007,7 @@ func TestCCPAInvalid(t *testing.T) { openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } ui := uint64(1) @@ -962,6 +1039,81 @@ func TestCCPAInvalid(t *testing.T) { assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } +func TestSanitizeRequest(t *testing.T) { + testCases := []struct { + description string + req *openrtb.BidRequest + ipValidator iputil.IPValidator + expectedIPv4 string + expectedIPv6 string + }{ + { + description: "Empty", + req: &openrtb.BidRequest{ + Device: &openrtb.Device{ + IP: "", + IPv6: "", + }, + }, + expectedIPv4: "", + expectedIPv6: "", + }, + { + description: "Valid", + req: &openrtb.BidRequest{ + Device: &openrtb.Device{ + IP: "1.1.1.1", + IPv6: "1111::", + }, + }, + ipValidator: hardcodedResponseIPValidator{response: true}, + expectedIPv4: "1.1.1.1", + expectedIPv6: "1111::", + }, + { + description: "Invalid", + req: &openrtb.BidRequest{ + Device: &openrtb.Device{ + IP: "1.1.1.1", + IPv6: "1111::", + }, + }, + ipValidator: hardcodedResponseIPValidator{response: false}, + expectedIPv4: "", + expectedIPv6: "", + }, + { + description: "Invalid - Wrong IP Types", + req: &openrtb.BidRequest{ + Device: &openrtb.Device{ + IP: "1111::", + IPv6: "1.1.1.1", + }, + }, + ipValidator: hardcodedResponseIPValidator{response: true}, + expectedIPv4: "", + expectedIPv6: "", + }, + { + description: "Malformed", + req: &openrtb.BidRequest{ + Device: &openrtb.Device{ + IP: "malformed", + IPv6: "malformed", + }, + }, + expectedIPv4: "", + expectedIPv6: "", + }, + } + + for _, test := range testCases { + sanitizeRequest(test.req, test.ipValidator) + assert.Equal(t, test.expectedIPv4, test.req.Device.IP, test.description+":ipv4") + assert.Equal(t, test.expectedIPv6, test.req.Device.IPv6, test.description+":ipv6") + } +} + // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { gotRequest *openrtb.BidRequest @@ -1385,3 +1537,11 @@ func newBidderInfo(cfg config.Adapter) adapters.BidderInfo { Status: status, } } + +type hardcodedResponseIPValidator struct { + response bool +} + +func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { + return v.response +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv4.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv4.json new file mode 100644 index 00000000000..feade898833 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv4.json @@ -0,0 +1,38 @@ +{ + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 600 + }] + }, + "pmp": { + "deals": [{ + "id": "some-deal-id" + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "device": { + "ip": "8.8.8.8" + }, + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + } + } + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv6.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv6.json new file mode 100644 index 00000000000..42d8d37b1cb --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-ipv6.json @@ -0,0 +1,38 @@ +{ + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 600 + }] + }, + "pmp": { + "deals": [{ + "id": "some-deal-id" + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "device": { + "ipv6": "8888::" + }, + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + } + } + } + } \ No newline at end of file diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 64c99fa5a3e..18678be541c 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -18,6 +18,7 @@ import ( jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/iputil" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -39,11 +40,32 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { return nil, errors.New("NewVideoEndpoint requires non-nil arguments.") } + defRequest := defReqJSON != nil && len(defReqJSON) > 0 + ipValidator := iputil.PublicNetworkIPValidator{ + IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, + IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, + } + videoEndpointRegexp := regexp.MustCompile(`[<>]`) - return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap, cache, videoEndpointRegexp}).VideoAuctionEndpoint), nil + return httprouter.Handle((&endpointDeps{ + ex, + validator, + requestsById, + videoFetcher, + categories, + cfg, + met, + pbsAnalytics, + disabledBidders, + defRequest, + defReqJSON, + bidderMap, + cache, + videoEndpointRegexp, + ipValidator}).VideoAuctionEndpoint), nil } /* diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 631cb277f7f..f29ac3bfed9 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1110,7 +1110,7 @@ func TestCCPA(t *testing.T) { func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} - edep := &endpointDeps{ + deps := &endpointDeps{ ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, @@ -1125,9 +1125,10 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p openrtb_ext.BidderMap, nil, nil, + hardcodedResponseIPValidator{response: true}, } - return edep, theMetrics, mockModule + return deps, theMetrics, mockModule } type mockAnalyticsModule struct { @@ -1151,7 +1152,7 @@ func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) { return } func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - edep := &endpointDeps{ + deps := &endpointDeps{ ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, @@ -1166,9 +1167,10 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { openrtb_ext.BidderMap, ex.cache, regexp.MustCompile(`[<>]`), + hardcodedResponseIPValidator{response: true}, } - return edep + return deps } type mockCacheClient struct { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 93cb60fb5af..161b24fd1c1 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -16,19 +16,18 @@ import ( "time" "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currencies" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" metricsConf "github.com/prebid/prebid-server/pbsmetrics/config" pbc "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" @@ -1837,7 +1836,7 @@ func (c *wellBehavedCache) GetExtCacheData() (string, string) { return "www.pbcserver.com", "/pbcache/endpoint" } -func (c *wellBehavedCache) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) { +func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) { ids := make([]string, len(values)) for i := 0; i < len(values); i++ { ids[i] = strconv.Itoa(i) diff --git a/main_test.go b/main_test.go index d7dc9dd24a0..70eea2825f0 100644 --- a/main_test.go +++ b/main_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" "github.com/spf13/viper" ) @@ -56,10 +57,11 @@ func TestViperEnv(t *testing.T) { ttl := forceEnv(t, "PBS_HOST_COOKIE_TTL_DAYS", "60") defer ttl() - // Basic config set - compareStrings(t, "Viper error: port expected to be %s, found %s", "7777", v.Get("port").(string)) - // Nested config set - compareStrings(t, "Viper error: adapters.pubmatic.endpoint expected to be %s, found %s", "not_an_endpoint", v.Get("adapters.pubmatic.endpoint").(string)) - // Config set with underscores - compareStrings(t, "Viper error: host_cookie.ttl_days expected to be %s, found %s", "60", v.Get("host_cookie.ttl_days").(string)) + ipv4Networks := forceEnv(t, "PBS_REQUEST_VALIDATION_IPV4_PRIVATE_NETWORKS", "1.1.1.1/24 2.2.2.2/24") + defer ipv4Networks() + + assert.Equal(t, 7777, v.Get("port"), "Basic Config") + assert.Equal(t, "not_an_endpoint", v.Get("adapters.pubmatic.endpoint"), "Nested Config") + assert.Equal(t, 60, v.Get("host_cookie.ttl_days"), "Config With Underscores") + assert.ElementsMatch(t, []string{"1.1.1.1/24", "2.2.2.2/24"}, v.Get("request_validation.ipv4_private_networks"), "Arrays") } diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index 9e79b62b38b..30f8bd25c0d 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -12,9 +12,10 @@ import ( "github.com/prebid/prebid-server/cache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/prebid" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/util/iputil" "github.com/blang/semver" "github.com/buger/jsonparser" @@ -216,6 +217,8 @@ func ParseMediaTypes(types []string) []MediaType { return mtypes } +var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{iputil.IPv4} + func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { defer r.Body.Close() @@ -235,7 +238,9 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C if pbsReq.Device == nil { pbsReq.Device = &openrtb.Device{} } - pbsReq.Device.IP = prebid.GetIP(r) + if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { + pbsReq.Device.IP = ip.String() + } if pbsReq.SDK == nil { pbsReq.SDK = &SDK{} @@ -291,7 +296,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C pbsReq.IsDebug = true } - if prebid.IsSecure(r) { + if httputil.IsSecure(r) { pbsReq.Secure = 1 } diff --git a/prebid/prebid.go b/prebid/prebid.go deleted file mode 100644 index 68c4a48c2c8..00000000000 --- a/prebid/prebid.go +++ /dev/null @@ -1,82 +0,0 @@ -package prebid - -import ( - "net" - "net/http" - "strings" -) - -var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") -var xRealIP = http.CanonicalHeaderKey("X-Real-IP") -var xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto") - -// IsSecure attempts to detect whether the request is https -func IsSecure(r *http.Request) bool { - // lowercase for case-insensitive match for X-Forwarded-Proto header - if strings.ToLower(r.Header.Get(xForwardedProto)) == "https" { - return true - } - // ensure that URL.Scheme is lowercase (it should be "https") - if strings.ToLower(r.URL.Scheme) == "https" { - return true - } - // use strings.HasPrefix because a valid example is "HTTP/1.0" - if strings.HasPrefix(r.Proto, "HTTPS") { - return true - } - // check if TLS is not-nil as a final fallback - if r.TLS != nil { - return true - } - return false -} - -// GetIP will attempt to get the IP Address by first checking headers -// and then falling back on the RemoteAddr -func GetIP(r *http.Request) string { - // first check headers - if ip := GetForwardedIP(r); ip != "" { - return ip - } - // next try to parse the RemoteAddr. - // if err is not nil then weird hosts might appear as the ip: https://github.com/golang/go/issues/14827 - if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { - return ip - } - return "" -} - -// GetForwardedIP will return back X-Forwarded-For or X-Real-IP (if set) -func GetForwardedIP(r *http.Request) string { - // first attempt to parse X-Forwarded-For - if ip := getForwardedFor(r); ip != "" { - return ip - } - // if we don't have X-Forwarded-For then try X-Real-IP - if ip := getRealIP(r); ip != "" { - return ip - } - return "" -} - -// getForwardedFor will attempt to parse the X-Forwarded-For header -func getForwardedFor(r *http.Request) string { - if xff := r.Header.Get(xForwardedFor); xff != "" { - // X-Forwarded-For: client1, proxy1, proxy2 - i := strings.Index(xff, ", ") - if i == -1 { - i = len(xff) - } - return xff[:i] - } - return "" -} - -// getRealIP will attempt to parse the X-Real-IP header -// Header.Get is case-insensitive -func getRealIP(r *http.Request) string { - if xrip := r.Header.Get(xRealIP); xrip != "" { - return xrip - } - return "" -} diff --git a/util/httputil/httputil.go b/util/httputil/httputil.go new file mode 100644 index 00000000000..461512771b3 --- /dev/null +++ b/util/httputil/httputil.go @@ -0,0 +1,99 @@ +package httputil + +import ( + "net" + "net/http" + "strings" + + "github.com/prebid/prebid-server/util/iputil" +) + +var ( + trueClientIP = http.CanonicalHeaderKey("True-Client-IP") + xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto") + xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") + xRealIP = http.CanonicalHeaderKey("X-Real-IP") +) + +const ( + https = "https" +) + +// IsSecure determines if a http request uses https. +func IsSecure(r *http.Request) bool { + if strings.EqualFold(r.Header.Get(xForwardedProto), https) { + return true + } + + if strings.EqualFold(r.URL.Scheme, https) { + return true + } + + if r.TLS != nil { + return true + } + + return false +} + +// FindIP returns the first ip address found in the http request matching the predicate v. +func FindIP(r *http.Request, v iputil.IPValidator) (net.IP, iputil.IPVersion) { + if ip, ver := findTrueClientIP(r, v); ip != nil { + return ip, ver + } + + if ip, ver := findForwardedFor(r, v); ip != nil { + return ip, ver + } + + if ip, ver := findRealIP(r, v); ip != nil { + return ip, ver + } + + if ip, ver := findRemoteAddr(r, v); ip != nil { + return ip, ver + } + + return nil, iputil.IPvUnknown +} + +func findTrueClientIP(r *http.Request, v iputil.IPValidator) (net.IP, iputil.IPVersion) { + if value := r.Header.Get(trueClientIP); value != "" { + value = strings.TrimSpace(value) + if ip, ver := iputil.ParseIP(value); ip != nil && v.IsValid(ip, ver) { + return ip, ver + } + } + return nil, iputil.IPvUnknown +} + +func findForwardedFor(r *http.Request, v iputil.IPValidator) (net.IP, iputil.IPVersion) { + if value := r.Header.Get(xForwardedFor); value != "" { + for _, p := range strings.Split(value, ",") { + p = strings.TrimSpace(p) + if ip, ver := iputil.ParseIP(p); ip != nil && v.IsValid(ip, ver) { + return ip, ver + } + } + } + return nil, iputil.IPvUnknown +} + +func findRealIP(r *http.Request, v iputil.IPValidator) (net.IP, iputil.IPVersion) { + if value := r.Header.Get(xRealIP); value != "" { + value = strings.TrimSpace(value) + if ip, ver := iputil.ParseIP(value); ip != nil && v.IsValid(ip, ver) { + return ip, ver + } + } + return nil, iputil.IPvUnknown +} + +func findRemoteAddr(r *http.Request, v iputil.IPValidator) (net.IP, iputil.IPVersion) { + if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + if ip, ver := iputil.ParseIP(host); ip != nil && v.IsValid(ip, ver) { + return ip, ver + } + } + return nil, iputil.IPvUnknown +} diff --git a/util/httputil/httputil_test.go b/util/httputil/httputil_test.go new file mode 100644 index 00000000000..f7166740fe5 --- /dev/null +++ b/util/httputil/httputil_test.go @@ -0,0 +1,327 @@ +package httputil + +import ( + "crypto/tls" + "net" + "net/http" + "testing" + + "github.com/prebid/prebid-server/util/iputil" + "github.com/stretchr/testify/assert" +) + +func TestIsSecure(t *testing.T) { + testCases := []struct { + description string + url string + xForwardedProto string + tls bool + expectIsSecure bool + }{ + { + description: "HTTP", + url: "http://host.com", + expectIsSecure: false, + }, + { + description: "HTTPS - Forwarded Protocol", + url: "http://host.com", + xForwardedProto: "https", + expectIsSecure: true, + }, + { + description: "HTTPS - Forwarded Protocol - Case Insensitive", + url: "http://host.com", + xForwardedProto: "HTTPS", + expectIsSecure: true, + }, + { + description: "HTTPS - Protocol", + url: "https://host.com", + expectIsSecure: true, + }, + { + description: "HTTPS - Protocol - Case Insensitive", + url: "HTTPS://host.com", + expectIsSecure: true, + }, + { + description: "HTTPS - TLS", + url: "http://host.com", + tls: true, + expectIsSecure: true, + }, + } + + for _, test := range testCases { + request, err := http.NewRequest("GET", test.url, nil) + if err != nil { + t.Fatalf("Unable to create test http request. Err: %v", err) + } + if test.xForwardedProto != "" { + request.Header.Add("X-Forwarded-Proto", test.xForwardedProto) + } + if test.tls { + request.TLS = &tls.ConnectionState{} + } + + result := IsSecure(request) + + assert.Equal(t, test.expectIsSecure, result, test.description) + } +} + +func TestFindIP(t *testing.T) { + alwaysTrue := hardcodedResponseIPValidator{response: true} + alwaysFalse := hardcodedResponseIPValidator{response: false} + + testCases := []struct { + description string + trueClientIP string + xForwardedFor string + xRealIP string + remoteAddr string + validator iputil.IPValidator + expectedIP net.IP + expectedVer iputil.IPVersion + }{ + { + description: "No Address", + expectedIP: nil, + expectedVer: iputil.IPvUnknown, + }, + { + description: "False Validator - IPv4", + trueClientIP: "1.1.1.1", + xForwardedFor: "2.2.2.2, 3.3.3.3", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysFalse, + expectedIP: nil, + expectedVer: iputil.IPvUnknown, + }, + { + description: "False Validator - IPv6", + trueClientIP: "1111::", + xForwardedFor: "2222::, 3333::", + xRealIP: "4444::", + remoteAddr: "[5555::]:5]", + validator: alwaysFalse, + expectedIP: nil, + expectedVer: iputil.IPvUnknown, + }, + { + description: "True Validator - IPv4 - True Client IP", + trueClientIP: "1.1.1.1", + xForwardedFor: "2.2.2.2, 3.3.3.3", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("1.1.1.1"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - True Client IP - Ignore Whitespace", + trueClientIP: " 1.1.1.1 ", + xForwardedFor: "2.2.2.2, 3.3.3.3", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("1.1.1.1"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - X Forwarded For", + trueClientIP: "", + xForwardedFor: "2.2.2.2, 3.3.3.3", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("2.2.2.2"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - X Forwarded For - Ignore Whitespace", + trueClientIP: "", + xForwardedFor: " 2.2.2.2, 3.3.3.3 ", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("2.2.2.2"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - X Real IP", + trueClientIP: "", + xForwardedFor: "", + xRealIP: "4.4.4.4", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("4.4.4.4"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - X Real IP - Ignore Whitespace", + trueClientIP: "", + xForwardedFor: "", + xRealIP: " 4.4.4.4 ", + remoteAddr: "5.5.5.5:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("4.4.4.4"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv4 - Remote Address", + trueClientIP: "", + xForwardedFor: "", + xRealIP: "", + remoteAddr: "5.5.5.5:80", + validator: alwaysTrue, + expectedIP: net.ParseIP("5.5.5.5"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - IPv6 - True Client IP", + trueClientIP: "1111::", + xForwardedFor: "2222::, 3333::", + xRealIP: "4444::", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("1111::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - True Client IP - Ignore Whitespace", + trueClientIP: " 1111:: ", + xForwardedFor: "2222::, 3333::", + xRealIP: "4444::", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("1111::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - X Forwarded For", + trueClientIP: "", + xForwardedFor: "2222::, 3333::", + xRealIP: "4444::", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("2222::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - X Forwarded For - Ignore Whitespace", + trueClientIP: "", + xForwardedFor: " 2222::, 3333:: ", + xRealIP: "4444::", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("2222::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - X Real IP", + trueClientIP: "", + xForwardedFor: "", + xRealIP: "4444::", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("4444::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - X Real IP - Ignore Whitespace", + trueClientIP: "", + xForwardedFor: "", + xRealIP: " 4444:: ", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("4444::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - IPv6 - Remote Address", + trueClientIP: "", + xForwardedFor: "", + xRealIP: "", + remoteAddr: "[5555::]:5", + validator: alwaysTrue, + expectedIP: net.ParseIP("5555::"), + expectedVer: iputil.IPv6, + }, + { + description: "True Validator - Malformed - All", + trueClientIP: "malformed", + xForwardedFor: "malformed", + xRealIP: "malformed", + remoteAddr: "malformed", + validator: alwaysTrue, + expectedIP: nil, + expectedVer: iputil.IPvUnknown, + }, + { + description: "True Validator - Malformed - Some", + trueClientIP: "malformed", + xForwardedFor: "malformed", + xRealIP: "4.4.4.4", + remoteAddr: "malformed", + validator: alwaysTrue, + expectedIP: net.ParseIP("4.4.4.4"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - Malformed - X Forwarded For - IPv4", + trueClientIP: "malformed", + xForwardedFor: "malformed, 4.4.4.4, 3333::, malformed", + xRealIP: "malformed", + remoteAddr: "malformed", + validator: alwaysTrue, + expectedIP: net.ParseIP("4.4.4.4"), + expectedVer: iputil.IPv4, + }, + { + description: "True Validator - Malformed - X Forwarded For - IPv6", + trueClientIP: "malformed", + xForwardedFor: "malformed, 3333::, 4.4.4.4, malformed", + xRealIP: "malformed", + remoteAddr: "malformed", + validator: alwaysTrue, + expectedIP: net.ParseIP("3333::"), + expectedVer: iputil.IPv6, + }, + } + + for _, test := range testCases { + // Build Request + request, err := http.NewRequest("GET", "http://anyurl.com", nil) + if err != nil { + t.Fatalf("Unable to create test http request. Err: %v", err) + } + if test.trueClientIP != "" { + request.Header.Add("True-Client-IP", test.trueClientIP) + } + if test.xForwardedFor != "" { + request.Header.Add("X-Forwarded-For", test.xForwardedFor) + } + if test.xRealIP != "" { + request.Header.Add("X-Real-IP", test.xRealIP) + } + request.RemoteAddr = test.remoteAddr + + // Run Test + ip, ver := FindIP(request, test.validator) + + // Assertions + assert.Equal(t, test.expectedIP, ip, test.description+":ip") + assert.Equal(t, test.expectedVer, ver, test.description+":ver") + } +} + +type hardcodedResponseIPValidator struct { + response bool +} + +func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { + return v.response +} diff --git a/util/iputil/parse.go b/util/iputil/parse.go new file mode 100644 index 00000000000..bcb00760e22 --- /dev/null +++ b/util/iputil/parse.go @@ -0,0 +1,27 @@ +package iputil + +import ( + "net" + "strings" +) + +// IPVersion is the numerical version of the IP address spec (4 or 6). +type IPVersion int + +// IP address versions. +const ( + IPvUnknown IPVersion = 0 + IPv4 IPVersion = 4 + IPv6 IPVersion = 6 +) + +// ParseIP parses v as an ip address returning the result and version, or nil and unknown if invalid. +func ParseIP(v string) (net.IP, IPVersion) { + if ip := net.ParseIP(v); ip != nil { + if strings.ContainsRune(v, ':') { + return ip, IPv6 + } + return ip, IPv4 + } + return nil, IPvUnknown +} diff --git a/util/iputil/parse_test.go b/util/iputil/parse_test.go new file mode 100644 index 00000000000..53431b0f2a9 --- /dev/null +++ b/util/iputil/parse_test.go @@ -0,0 +1,30 @@ +package iputil + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseIP(t *testing.T) { + testCases := []struct { + input string + expectedVer IPVersion + expectedIP net.IP + }{ + {"", IPvUnknown, nil}, + {"1.1.1.1", IPv4, net.IPv4(1, 1, 1, 1)}, + {"-1.-1.-1.-1", IPvUnknown, nil}, + {"256.256.256.256", IPvUnknown, nil}, + {"::ffff:1.1.1.1", IPv6, net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 1, 1, 1, 1}}, + {"0101::", IPv6, net.IP{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"zzzz::", IPvUnknown, nil}, + } + + for _, test := range testCases { + ip, ver := ParseIP(test.input) + assert.Equal(t, test.expectedVer, ver) + assert.Equal(t, test.expectedIP, ip) + } +} diff --git a/util/iputil/validator.go b/util/iputil/validator.go new file mode 100644 index 00000000000..e4b822f0c7c --- /dev/null +++ b/util/iputil/validator.go @@ -0,0 +1,48 @@ +package iputil + +import ( + "net" +) + +// IPValidator is the interface for validating an ip address and version. +type IPValidator interface { + // IsValid returns true when an IP address is determined to be valid. + IsValid(net.IP, IPVersion) bool +} + +// PublicNetworkIPValidator validates an ip address which is not contained in the list of known private networks. +type PublicNetworkIPValidator struct { + IPv4PrivateNetworks []net.IPNet + IPv6PrivateNetworks []net.IPNet +} + +// IsValid implements the IPValidator interface. +func (v PublicNetworkIPValidator) IsValid(ip net.IP, ver IPVersion) bool { + var privateNetworks []net.IPNet + switch ver { + case IPv4: + privateNetworks = v.IPv4PrivateNetworks + case IPv6: + privateNetworks = v.IPv6PrivateNetworks + default: + return false + } + + for _, ipNet := range privateNetworks { + if ipNet.Contains(ip) { + return false + } + } + + return true +} + +// VersionIPValidator validates an ip address based on the desired ip version. +type VersionIPValidator struct { + Version IPVersion +} + +// IsValid implements the IPValidator interface. +func (v VersionIPValidator) IsValid(ip net.IP, ver IPVersion) bool { + return ver == v.Version +} diff --git a/util/iputil/validator_test.go b/util/iputil/validator_test.go new file mode 100644 index 00000000000..4419af22c04 --- /dev/null +++ b/util/iputil/validator_test.go @@ -0,0 +1,222 @@ +package iputil + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPublicNetworkIPValidator(t *testing.T) { + ipv4Network1 := net.IPNet{IP: net.ParseIP("1.0.0.0"), Mask: net.CIDRMask(8, 32)} + ipv4Network2 := net.IPNet{IP: net.ParseIP("2.0.0.0"), Mask: net.CIDRMask(8, 32)} + + ipv6Network1 := net.IPNet{IP: net.ParseIP("3300::"), Mask: net.CIDRMask(8, 128)} + ipv6Network2 := net.IPNet{IP: net.ParseIP("4400::"), Mask: net.CIDRMask(8, 128)} + + testCases := []struct { + description string + ip net.IP + ver IPVersion + ipv4PrivateNetworks []net.IPNet + ipv6PrivateNetworks []net.IPNet + expected bool + }{ + { + description: "IPv4 - Public - None", + ip: net.ParseIP("1.1.1.1"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: true, + }, + { + description: "IPv4 - Public - One", + ip: net.ParseIP("2.2.2.2"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: true, + }, + { + description: "IPv4 - Public - Many", + ip: net.ParseIP("3.3.3.3"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network2}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: true, + }, + { + description: "IPv4 - Private - One", + ip: net.ParseIP("1.1.1.1"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: false, + }, + { + description: "IPv4 - Private - Many", + ip: net.ParseIP("2.2.2.2"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network2}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: false, + }, + { + description: "IPv6 - Public - None", + ip: net.ParseIP("3333::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{}, + expected: true, + }, + { + description: "IPv6 - Public - One", + ip: net.ParseIP("4444::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1}, + expected: true, + }, + { + description: "IPv6 - Public - Many", + ip: net.ParseIP("5555::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: true, + }, + { + description: "IPv6 - Private - One", + ip: net.ParseIP("3333::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1}, + expected: false, + }, + { + description: "IPv6 - Private - Many", + ip: net.ParseIP("4444::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: false, + }, + { + description: "Mixed - Unknown", + ip: net.ParseIP("3.3.3.3"), + ver: IPvUnknown, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: false, + }, + { + description: "Mixed - Public - IPv4", + ip: net.ParseIP("3.3.3.3"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: true, + }, + { + description: "Mixed - Public - IPv6", + ip: net.ParseIP("5555::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: true, + }, + { + description: "Mixed - Private - IPv4", + ip: net.ParseIP("1.1.1.1"), + ver: IPv4, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: false, + }, + { + description: "Mixed - Private - IPv6", + ip: net.ParseIP("3333::"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{ipv4Network1, ipv4Network1}, + ipv6PrivateNetworks: []net.IPNet{ipv6Network1, ipv6Network2}, + expected: false, + }, + { + description: "Mixed - Public - IPv6 Encoded IPv4", + ip: net.ParseIP("::FFFF:1.1.1.1"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{{IP: net.ParseIP("1.0.0.0"), Mask: net.CIDRMask(8, 32)}}, + ipv6PrivateNetworks: []net.IPNet{{IP: net.ParseIP("::FFFF:2.0.0.0"), Mask: net.CIDRMask(108, 128)}}, + expected: true, + }, + { + description: "Mixed - Private - IPv6 Encoded IPv4", + ip: net.ParseIP("::FFFF:2.2.2.2"), + ver: IPv6, + ipv4PrivateNetworks: []net.IPNet{{IP: net.ParseIP("1.0.0.0"), Mask: net.CIDRMask(8, 32)}}, + ipv6PrivateNetworks: []net.IPNet{{IP: net.ParseIP("::FFFF:2.0.0.0"), Mask: net.CIDRMask(108, 128)}}, + expected: false, + }, + } + + for _, test := range testCases { + requestValidation := PublicNetworkIPValidator{ + IPv4PrivateNetworks: test.ipv4PrivateNetworks, + IPv6PrivateNetworks: test.ipv6PrivateNetworks, + } + + result := requestValidation.IsValid(test.ip, test.ver) + + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestVersionIPValidator(t *testing.T) { + testCases := []struct { + description string + validatorVersion IPVersion + ip net.IP + ipVer IPVersion + expected bool + }{ + { + description: "IPv4", + validatorVersion: IPv4, + ip: net.ParseIP("1.1.1.1"), + ipVer: IPv4, + expected: true, + }, + { + description: "IPv4 - Given Unknown", + validatorVersion: IPv4, + ip: nil, + ipVer: IPvUnknown, + expected: false, + }, + { + description: "IPv6", + validatorVersion: IPv6, + ip: net.ParseIP("1111::"), + ipVer: IPv6, + expected: true, + }, + { + description: "IPv6 - Given Unknown", + validatorVersion: IPv6, + ip: nil, + ipVer: IPvUnknown, + expected: false, + }, + } + + for _, test := range testCases { + m := VersionIPValidator{ + Version: test.validatorVersion, + } + + result := m.IsValid(test.ip, test.ipVer) + + assert.Equal(t, test.expected, result) + } +} From 9b96f50afeb81a665668525ff1804d04c3b64ea2 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Mon, 29 Jun 2020 22:45:43 +0300 Subject: [PATCH 128/603] Change endpont address (#1370) * Adman adapter * add adman line to syner test * add tests * fix issues * fix web banner test * add 404 banner * fmt * rase coverage * del redundant files * change endpont address * change config endpoint Co-authored-by: Aiholkin --- adapters/adman/adman_test.go | 2 +- adapters/adman/admantest/exemplary/simple-banner.json | 6 +++--- adapters/adman/admantest/exemplary/simple-video.json | 2 +- adapters/adman/admantest/exemplary/simple-web-banner.json | 6 +++--- adapters/adman/admantest/supplemental/bad_response.json | 2 +- adapters/adman/admantest/supplemental/status-204.json | 2 +- adapters/adman/admantest/supplemental/status-404.json | 2 +- config/config.go | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index da0f37e9a48..4ef88933759 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -7,6 +7,6 @@ import ( ) func TestJsonSamples(t *testing.T) { - admanAdapter := NewAdmanBidder("http://eu-ams-1.admanmedia.com/?c=o&m=ortb") + admanAdapter := NewAdmanBidder("http://pub.admanmedia.com/?c=o&m=ortb") adapterstest.RunJSONBidderTest(t, "admantest", admanAdapter) } diff --git a/adapters/adman/admantest/exemplary/simple-banner.json b/adapters/adman/admantest/exemplary/simple-banner.json index 41f76e00645..8bbe16aa0fe 100644 --- a/adapters/adman/admantest/exemplary/simple-banner.json +++ b/adapters/adman/admantest/exemplary/simple-banner.json @@ -37,7 +37,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "imp": [ @@ -84,7 +84,7 @@ "id": "test_bid_id", "impid": "test-imp-id", "price": 0.27543, - "adm": "", + "adm": "", "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", @@ -114,7 +114,7 @@ "id": "test_bid_id", "impid": "test-imp-id", "price": 0.27543, - "adm": "", + "adm": "", "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", diff --git a/adapters/adman/admantest/exemplary/simple-video.json b/adapters/adman/admantest/exemplary/simple-video.json index d7fa82d274d..159a30a93e0 100644 --- a/adapters/adman/admantest/exemplary/simple-video.json +++ b/adapters/adman/admantest/exemplary/simple-video.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "device": { diff --git a/adapters/adman/admantest/exemplary/simple-web-banner.json b/adapters/adman/admantest/exemplary/simple-web-banner.json index ce872bff52b..0ceaac7c6d5 100644 --- a/adapters/adman/admantest/exemplary/simple-web-banner.json +++ b/adapters/adman/admantest/exemplary/simple-web-banner.json @@ -36,7 +36,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "imp": [ @@ -82,7 +82,7 @@ "id": "test_bid_id", "impid": "test-imp-id", "price": 0.27543, - "adm": "", + "adm": "", "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", @@ -112,7 +112,7 @@ "id": "test_bid_id", "impid": "test-imp-id", "price": 0.27543, - "adm": "", + "adm": "", "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", diff --git a/adapters/adman/admantest/supplemental/bad_response.json b/adapters/adman/admantest/supplemental/bad_response.json index 8c349297e73..d5a28c74256 100644 --- a/adapters/adman/admantest/supplemental/bad_response.json +++ b/adapters/adman/admantest/supplemental/bad_response.json @@ -35,7 +35,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adman/admantest/supplemental/status-204.json b/adapters/adman/admantest/supplemental/status-204.json index 7f9a12dec29..72b28bffdcf 100644 --- a/adapters/adman/admantest/supplemental/status-204.json +++ b/adapters/adman/admantest/supplemental/status-204.json @@ -35,7 +35,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/adman/admantest/supplemental/status-404.json b/adapters/adman/admantest/supplemental/status-404.json index 560878342f0..043afbdc1dc 100644 --- a/adapters/adman/admantest/supplemental/status-404.json +++ b/adapters/adman/admantest/supplemental/status-404.json @@ -35,7 +35,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://eu-ams-1.admanmedia.com/?c=o&m=ortb", + "uri": "http://pub.admanmedia.com/?c=o&m=ortb", "body": { "id": "test-request-id", "imp": [ diff --git a/config/config.go b/config/config.go index 50cfbb1c170..16bab2996be 100755 --- a/config/config.go +++ b/config/config.go @@ -773,7 +773,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}") - v.SetDefault("adapters.adman.endpoint", "http://eu-ams-1.admanmedia.com/?c=o&m=ortb") + v.SetDefault("adapters.adman.endpoint", "http://pub.admanmedia.com/?c=o&m=ortb") v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") From 919d29ac3a6a29a55baa392d0dbbb88872ddd3c4 Mon Sep 17 00:00:00 2001 From: Florian Hartwig Date: Tue, 30 Jun 2020 16:29:43 +0200 Subject: [PATCH 129/603] Don't override test parameter (#1373) --- adapters/pubnative/pubnative.go | 1 - adapters/pubnative/pubnativetest/exemplary/simple-banner.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 4dc92920d8e..777ac4a05ed 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -83,7 +83,6 @@ func checkRequest(request *openrtb.BidRequest) error { } } - request.Test = 0 // don't forward test flag to PN adserver return nil } diff --git a/adapters/pubnative/pubnativetest/exemplary/simple-banner.json b/adapters/pubnative/pubnativetest/exemplary/simple-banner.json index 7c7d1319a50..5297cd3284d 100644 --- a/adapters/pubnative/pubnativetest/exemplary/simple-banner.json +++ b/adapters/pubnative/pubnativetest/exemplary/simple-banner.json @@ -111,6 +111,7 @@ }, "at": 1, "tmax": 200, + "test": 1, "source": { "tid": "283746293874293" }, From e430c629b140d263f0c38195862fae5661bb785f Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 30 Jun 2020 12:44:01 -0400 Subject: [PATCH 130/603] OpenX + Facebook Hardening (#1368) --- adapters/adapterstest/test_json.go | 6 +- .../exemplary/banner-app.json | 116 +++++++++++++++ .../{banner.json => banner-site.json} | 6 - .../exemplary/interstitial.json | 6 - .../exemplary/native-1.1.json | 6 - .../audienceNetworktest/exemplary/video.json | 6 - .../supplemental/banner-format-only.json | 6 - .../supplemental/invalid-adm.json | 103 ++++++++++++++ .../supplemental/invalid-interstitial.json | 40 ++++++ .../supplemental/missing-adm-bidid.json | 107 ++++++++++++++ .../supplemental/missing-adm.json | 106 ++++++++++++++ .../supplemental/multi-imp.json | 12 -- .../supplemental/no-bid-204.json | 6 - .../supplemental/no-imps.json | 22 +++ .../supplemental/server-error-500.json | 87 ++++++++++++ .../supplemental/split-placementId.json | 6 - adapters/audienceNetwork/facebook.go | 132 +++++++++--------- adapters/audienceNetwork/facebook_test.go | 31 +++- adapters/openx/openx.go | 6 +- exchange/adapter_map.go | 1 - util/maputil/maputil.go | 21 +++ util/maputil/maputil_test.go | 113 +++++++++++++++ 22 files changed, 813 insertions(+), 132 deletions(-) create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/banner-app.json rename adapters/audienceNetwork/audienceNetworktest/exemplary/{banner.json => banner-site.json} (96%) create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json create mode 100644 util/maputil/maputil.go create mode 100644 util/maputil/maputil_test.go diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 8fdb9c5d9d6..a25a4f1905a 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -164,14 +164,16 @@ type httpRequest struct { } type httpResponse struct { - Status int `json:"status"` - Body json.RawMessage `json:"body"` + Status int `json:"status"` + Body json.RawMessage `json:"body"` + Headers http.Header `json:"headers"` } func (resp *httpResponse) ToResponseData(t *testing.T) *adapters.ResponseData { return &adapters.ResponseData{ StatusCode: resp.Status, Body: resp.Body, + Headers: resp.Headers, } } diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-app.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-app.json new file mode 100644 index 00000000000..3ac62d90cd4 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-app.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "app": { + "id": "app-abc", + "bundle": "com.prebid" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [{ + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "w": -1, + "h": 250 + }, + "tagid": "123_456" + }], + "app": { + "id": "app-abc", + "bundle": "com.prebid", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [{ + "bid": [{ + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }] + }], + "bidid": "654", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "987", + "impid": "test-imp-id", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "987", + "crid": "987", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json similarity index 96% rename from adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json rename to adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json index f5f92515e26..01bab3dfd71 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json @@ -62,12 +62,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json index bad228d5f18..9f563f11948 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json @@ -64,12 +64,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json index 9090d80d099..16bed344767 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json @@ -56,12 +56,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json index 22c62f8b821..5ece0f08530 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json @@ -66,12 +66,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json index 3edd6569258..5469fefbd65 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json @@ -64,12 +64,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json new file mode 100644 index 00000000000..f145f5fe4ce --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [{ + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "w": -1, + "h": 250 + }, + "tagid": "123_456" + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [{ + "bid": [{ + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "malformed", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }] + }], + "bidid": "654", + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [{ + "value": "invalid character 'm' looking for beginning of value", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json new file mode 100644 index 00000000000..ad19d94c6e9 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 940, + "h": 560 + }, + "instl": 1, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "expectedMakeRequestsErrors": [{ + "value": "imp #test-imp-id: interstitial imps are only supported for banner", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json new file mode 100644 index 00000000000..b57c900104e --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [{ + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "w": -1, + "h": 250 + }, + "tagid": "123_456" + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [{ + "bid": [{ + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }] + }], + "bidid": "654", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [] + }], + "expectedMakeBidsErrors": [{ + "value": "bid 987 missing 'bid_id' in 'adm'", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json new file mode 100644 index 00000000000..23227aab959 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [{ + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "w": -1, + "h": 250 + }, + "tagid": "123_456" + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [{ + "bid": [{ + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }] + }], + "bidid": "654", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [] + }], + "expectedMakeBidsErrors": [{ + "value": "Bid 987 missing 'adm'", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json index 16e8aede10c..231c2826548 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json @@ -81,12 +81,6 @@ "tagid": "pub1_plmt1" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", @@ -158,12 +152,6 @@ "tagid": "pub2_plmt2" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json index bb192aad76f..45b35e05dd9 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json @@ -56,12 +56,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json new file mode 100644 index 00000000000..7420f7e8fb2 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json @@ -0,0 +1,22 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "expectedMakeRequestsErrors": [{ + "value": "No impressions provided", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json new file mode 100644 index 00000000000..7ff8886139a --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [{ + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "w": -1, + "h": -1 + }, + "tagid": "123_456" + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + } + }, + "mockResponse": { + "headers": { + "X-Fb-An-Errors": [ + "someError" + ]}, + "status": 500 + } + }], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code 500 with error message 'someError'", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json index 4c561c55276..34c1eccc58e 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json @@ -50,12 +50,6 @@ "tagid": "123_456" } ], - "ext": { - "appnexus": { - "hb_source": 5 - }, - "prebid": {} - }, "site": { "domain": "prebid.org", "page": "prebid.org", diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 9edb9a7d57e..f4091e4e23c 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -10,16 +10,17 @@ import ( "net/http" "strings" - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/maputil" + + "github.com/buger/jsonparser" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb" ) type FacebookAdapter struct { - http *adapters.HTTPAdapter URI string nonSecureUri string platformID string @@ -35,15 +36,6 @@ var supportedBannerHeights = map[uint64]bool{ 250: true, } -// used for cookies and such -func (a *FacebookAdapter) Name() string { - return "audienceNetwork" -} - -func (a *FacebookAdapter) SkipNoCookies() bool { - return false -} - type facebookReqExt struct { PlatformID string `json:"platformid"` AuthID string `json:"authentication_id"` @@ -178,8 +170,10 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { } } - switch impType { - case openrtb_ext.BidTypeBanner: + if impType == openrtb_ext.BidTypeBanner { + bannerCopy := *out.Banner + out.Banner = &bannerCopy + if out.Instl == 1 { out.Banner.W = openrtb.Uint64Ptr(0) out.Banner.H = openrtb.Uint64Ptr(0) @@ -212,7 +206,6 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { /* This will get overwritten post-serialization */ out.Banner.W = openrtb.Uint64Ptr(0) out.Banner.Format = nil - break } return nil @@ -239,102 +232,106 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (str } } - placementId := fbExt.PlacementId - publisherId := fbExt.PublisherId + placementID := fbExt.PlacementId + publisherID := fbExt.PublisherId // Support the legacy path with the caller was expected to pass in just placementId // which was an underscore concantenated string with the publisherId and placementId. // The new path for callers is to pass in the placementId and publisherId independently // and the below code will prefix the placementId that we pass to FAN with the publsiherId // so that we can abstract the implementation details from the caller - toks := strings.Split(placementId, "_") + toks := strings.Split(placementID, "_") if len(toks) == 1 { - if publisherId == "" { + if publisherID == "" { return "", "", &errortypes.BadInput{ Message: "Missing publisherId param", } } - return placementId, publisherId, nil + return placementID, publisherID, nil } else if len(toks) == 2 { - publisherId = toks[0] - placementId = toks[1] + publisherID = toks[0] + placementID = toks[1] } else { return "", "", &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid placementId param '%s' and publisherId param '%s'", placementId, publisherId), + Message: fmt.Sprintf("Invalid placementId param '%s' and publisherId param '%s'", placementID, publisherID), } } - return placementId, publisherId, nil + return placementID, publisherID, nil } // XXX: This entire function is just a hack to get around mxmCherry 11.0.0 limitations, without // having to fork the library and maintain our own branch -func modifyImpCustom(json []byte, imp *openrtb.Imp) ([]byte, error) { +func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { impType, ok := resolveImpType(imp) if ok == false { panic("processing an invalid impression") } - var err error + var jsonMap map[string]interface{} + err := json.Unmarshal(jsonData, &jsonMap) + if err != nil { + return jsonData, err + } + + var impMap map[string]interface{} + if impSlice, ok := maputil.ReadEmbeddedSlice(jsonMap, "imp"); !ok { + return jsonData, errors.New("unable to find imp in json data") + } else if len(impSlice) == 0 { + return jsonData, errors.New("unable to find imp[0] in json data") + } else if impMap, ok = impSlice[0].(map[string]interface{}); !ok { + return jsonData, errors.New("unexpected type for imp[0] found in json data") + } switch impType { case openrtb_ext.BidTypeBanner: - // The current version of mxmCherry (11.0.0) repesents banner.w as unsigned - // integers, so setting a value of -1 is not possible which is why we have to do it + // The current version of mxmCherry (11.0.0) represents banner.w as an unsigned + // integer, so setting a value of -1 is not possible which is why we have to do it // post-serialization - - // The above does not apply to interstitial impressions - if imp.Instl == 1 { - break - } - - json, err = jsonparser.Set(json, []byte("-1"), "imp", "[0]", "banner", "w") - if err != nil { - return json, err + isInterstitial := imp.Instl == 1 + if !isInterstitial { + if bannerMap, ok := maputil.ReadEmbeddedMap(impMap, "banner"); ok { + bannerMap["w"] = json.RawMessage("-1") + } else { + return jsonData, errors.New("unable to find imp[0].banner in json data") + } } - break - case openrtb_ext.BidTypeVideo: // mxmCherry omits video.w/h if set to zero, so we need to force set those // fields to zero post-serialization for the time being - json, err = jsonparser.Set(json, []byte("0"), "imp", "[0]", "video", "w") - if err != nil { - return json, err + if videoMap, ok := maputil.ReadEmbeddedMap(impMap, "video"); ok { + videoMap["w"] = json.RawMessage("0") + videoMap["h"] = json.RawMessage("0") + } else { + return jsonData, errors.New("unable to find imp[0].video in json data") } - json, err = jsonparser.Set(json, []byte("0"), "imp", "[0]", "video", "h") - if err != nil { - return json, err + case openrtb_ext.BidTypeNative: + nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") + if !ok { + return jsonData, errors.New("unable to find imp[0].video in json data") } - break - - case openrtb_ext.BidTypeNative: // Set w/h to -1 for native impressions based on the facebook native spec. // We have to set this post-serialization since the OpenRTB protocol doesn't - // actaully support w/h in the native object - json, err = jsonparser.Set(json, []byte("-1"), "imp", "[0]", "native", "w") - if err != nil { - return json, err - } - - json, err = jsonparser.Set(json, []byte("-1"), "imp", "[0]", "native", "h") - if err != nil { - return json, err - } + // actually support w/h in the native object + nativeMap["w"] = json.RawMessage("-1") + nativeMap["h"] = json.RawMessage("-1") // The FAN adserver does not expect the native request payload, all that information // is derived server side based on the placement ID. We need to remove these pieces of // information manually since OpenRTB (and thus mxmCherry) never omit native.request - json = jsonparser.Delete(json, "imp", "[0]", "native", "ver") - json = jsonparser.Delete(json, "imp", "[0]", "native", "request") - - break + delete(nativeMap, "ver") + delete(nativeMap, "request") } - return json, nil + if jsonReEncoded, err := json.Marshal(jsonMap); err == nil { + return jsonReEncoded, nil + } else { + return nil, fmt.Errorf("unable to encode json data (%v)", err) + } } func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -430,7 +427,7 @@ func resolveImpType(imp *openrtb.Imp) (openrtb_ext.BidType, bool) { return openrtb_ext.BidTypeBanner, false } -func NewFacebookBidder(client *http.Client, platformID string, appSecret string) adapters.Bidder { +func NewFacebookBidder(platformID string, appSecret string) adapters.Bidder { if platformID == "" { glog.Errorf("No facebook partnerID specified. Calls to the Audience Network will fail. Did you set adapters.facebook.platform_id in the app config?") return &adapters.MisconfiguredBidder{ @@ -447,11 +444,8 @@ func NewFacebookBidder(client *http.Client, platformID string, appSecret string) } } - a := &adapters.HTTPAdapter{Client: client} - return &FacebookAdapter{ - http: a, - URI: "https://an.facebook.com/placementbid.ortb", + URI: "https://an.facebook.com/placementbid.ortb", //for AB test nonSecureUri: "http://an.facebook.com/placementbid.ortb", platformID: platformID, diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 784a540e596..7f567d6137b 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -1,6 +1,7 @@ package audienceNetwork import ( + "errors" "testing" "time" @@ -40,14 +41,14 @@ type FacebookExt struct { } func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder(nil, "test-platform-id", "test-app-secret")) + adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder("test-platform-id", "test-app-secret")) } func TestMakeTimeoutNoticeApp(t *testing.T) { req := adapters.RequestData{ Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"app":{"publisher":{"id":"5678"}}}`), } - fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + fba := NewFacebookBidder("test-platform-id", "test-app-secret") tb, ok := fba.(adapters.TimeoutBidder) if !ok { @@ -64,7 +65,7 @@ func TestMakeTimeoutNoticeSite(t *testing.T) { req := adapters.RequestData{ Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`), } - fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + fba := NewFacebookBidder("test-platform-id", "test-app-secret") tb, ok := fba.(adapters.TimeoutBidder) if !ok { @@ -81,7 +82,7 @@ func TestMakeTimeoutNoticeBadRequest(t *testing.T) { req := adapters.RequestData{ Body: []byte(`{"imp":[{{"id":"1234"}}`), } - fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret") + fba := NewFacebookBidder("test-platform-id", "test-app-secret") tb, ok := fba.(adapters.TimeoutBidder) if !ok { @@ -93,3 +94,25 @@ func TestMakeTimeoutNoticeBadRequest(t *testing.T) { assert.NotNil(t, err, "Facebook MakeTimeoutNotification() did not return an error") } + +func TestNewFacebookBidderMissingPlatformID(t *testing.T) { + result := NewFacebookBidder("", "anyAppSecret") + + expected := &adapters.MisconfiguredBidder{ + Name: "audienceNetwork", + Error: errors.New("Audience Network is not configured properly on this Prebid Server deploy. If you believe this should work, contact the company hosting the service and tell them to check their configuration."), + } + + assert.Equal(t, expected, result) +} + +func TestNewFacebookBidderMissingAppSecret(t *testing.T) { + result := NewFacebookBidder("anyPlatformID", "") + + expected := &adapters.MisconfiguredBidder{ + Name: "audienceNetwork", + Error: errors.New("Audience Network is not configured properly on this Prebid Server deploy. If you believe this should work, contact the company hosting the service and tell them to check their configuration."), + } + + assert.Equal(t, expected, result) +} diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index 63297d0a4ee..ca88b18bdb8 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -143,11 +143,13 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { } if imp.Video != nil { + videoCopy := *imp.Video if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { - imp.Video.Ext = json.RawMessage(`{"rewarded":1}`) + videoCopy.Ext = json.RawMessage(`{"rewarded":1}`) } else { - imp.Video.Ext = nil + videoCopy.Ext = nil } + imp.Video = &videoCopy } return nil diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index c30bb0c622e..1f62d232233 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -122,7 +122,6 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( - client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppSecret), openrtb_ext.BidderGamma: gamma.NewGammaBidder(cfg.Adapters[string(openrtb_ext.BidderGamma)].Endpoint), diff --git a/util/maputil/maputil.go b/util/maputil/maputil.go new file mode 100644 index 00000000000..0d1d7dbb51c --- /dev/null +++ b/util/maputil/maputil.go @@ -0,0 +1,21 @@ +package maputil + +// ReadEmbeddedMap reads element k from the map m as a map[string]interface{}. +func ReadEmbeddedMap(m map[string]interface{}, k string) (map[string]interface{}, bool) { + if v, ok := m[k]; ok { + vCasted, ok := v.(map[string]interface{}) + return vCasted, ok + } + + return nil, false +} + +// ReadEmbeddedSlice reads element k from the map m as a []interface{}. +func ReadEmbeddedSlice(m map[string]interface{}, k string) ([]interface{}, bool) { + if v, ok := m[k]; ok { + vCasted, ok := v.([]interface{}) + return vCasted, ok + } + + return nil, false +} diff --git a/util/maputil/maputil_test.go b/util/maputil/maputil_test.go new file mode 100644 index 00000000000..2e6955cec9b --- /dev/null +++ b/util/maputil/maputil_test.go @@ -0,0 +1,113 @@ +package maputil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadEmbeddedMap(t *testing.T) { + testCases := []struct { + description string + value map[string]interface{} + key string + expectedMap map[string]interface{} + expectedOK bool + }{ + { + description: "Nil", + value: nil, + key: "", + expectedMap: nil, + expectedOK: false, + }, + { + description: "Empty", + value: map[string]interface{}{}, + key: "foo", + expectedMap: nil, + expectedOK: false, + }, + { + description: "Success", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": 42}}, + key: "foo", + expectedMap: map[string]interface{}{"bar": 42}, + expectedOK: true, + }, + { + description: "Not Found", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": 42}}, + key: "notFound", + expectedMap: nil, + expectedOK: false, + }, + { + description: "Wrong Type", + value: map[string]interface{}{"foo": 42}, + key: "foo", + expectedMap: nil, + expectedOK: false, + }, + } + + for _, test := range testCases { + resultMap, resultOK := ReadEmbeddedMap(test.value, test.key) + + assert.Equal(t, test.expectedMap, resultMap, test.description+":map") + assert.Equal(t, test.expectedOK, resultOK, test.description+":ok") + } +} + +func TestReadEmbeddedSlice(t *testing.T) { + testCases := []struct { + description string + value map[string]interface{} + key string + expectedSlice []interface{} + expectedOK bool + }{ + { + description: "Nil", + value: nil, + key: "", + expectedSlice: nil, + expectedOK: false, + }, + { + description: "Empty", + value: map[string]interface{}{}, + key: "foo", + expectedSlice: nil, + expectedOK: false, + }, + { + description: "Success", + value: map[string]interface{}{"foo": []interface{}{42}}, + key: "foo", + expectedSlice: []interface{}{42}, + expectedOK: true, + }, + { + description: "Not Found", + value: map[string]interface{}{"foo": []interface{}{42}}, + key: "notFound", + expectedSlice: nil, + expectedOK: false, + }, + { + description: "Wrong Type", + value: map[string]interface{}{"foo": 42}, + key: "foo", + expectedSlice: nil, + expectedOK: false, + }, + } + + for _, test := range testCases { + resultSlice, resultOK := ReadEmbeddedSlice(test.value, test.key) + + assert.Equal(t, test.expectedSlice, resultSlice, test.description+":slicd") + assert.Equal(t, test.expectedOK, resultOK, test.description+":ok") + } +} From 74af63b88166afd5bdcb2f388908a1f908855ff1 Mon Sep 17 00:00:00 2001 From: AaronColbyPrice <67345931+AaronColbyPrice@users.noreply.github.com> Date: Thu, 2 Jul 2020 07:58:50 -0700 Subject: [PATCH 131/603] Updating Conversant endpoint url (#1376) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 16bab2996be..bb2b909f5de 100755 --- a/config/config.go +++ b/config/config.go @@ -791,7 +791,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") - v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24") + v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") From 47c7a6b71d9f9cc8e2b66d218e5d896d0b614430 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:08:51 -0400 Subject: [PATCH 132/603] Metrics for TCF 2 adoption (#1360) --- exchange/exchange.go | 5 +++- exchange/utils.go | 20 +++++++++++++- exchange/utils_test.go | 6 ++--- go.sum | 3 +++ pbsmetrics/config/metrics.go | 11 ++++++++ pbsmetrics/go_metrics.go | 30 +++++++++++++++++++++ pbsmetrics/go_metrics_test.go | 4 +++ pbsmetrics/metrics.go | 30 +++++++++++++++++++++ pbsmetrics/metrics_mock.go | 5 ++++ pbsmetrics/prometheus/preload.go | 5 ++++ pbsmetrics/prometheus/prometheus.go | 27 ++++++++++++++++--- pbsmetrics/prometheus/prometheus_test.go | 34 ++++++++++++++++++++++-- pbsmetrics/prometheus/type_conversion.go | 9 +++++++ 13 files changed, 178 insertions(+), 11 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index d7eab0f4475..174a0b3e0fc 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -104,8 +104,11 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + cleanRequests, aliases, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + if cleanMetrics.gdprEnforced { + e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion)) + } // List of bidders we have requests for. liveAdapters := listBiddersWithRequests(cleanRequests) diff --git a/exchange/utils.go b/exchange/utils.go index 54122d13c09..96c00ec0e36 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,6 +6,8 @@ import ( "fmt" "math/rand" + "github.com/prebid/go-gdpr/vendorconsent" + "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/config" @@ -17,6 +19,15 @@ import ( "github.com/prebid/prebid-server/privacy/lmt" ) +// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just +// the TCF version, but made a struct to facilitate future expansion +type cleanMetrics struct { + // A simple flag if GDPR is being enforced on this request. + gdprEnforced bool + // a zero value means a missing or invalid GDPR string + gdprTcfVersion int +} + // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: // // 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder. @@ -29,7 +40,7 @@ func cleanOpenRTBRequests(ctx context.Context, labels pbsmetrics.Labels, gDPR gdpr.Permissions, usersyncIfAmbiguous bool, - privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) { + privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) { impsByBidder, errs := splitImps(orig.Imp) if len(errs) > 0 { @@ -64,6 +75,13 @@ func cleanOpenRTBRequests(ctx context.Context, LMT: lmtPolicy.ShouldEnforce(), } + if gdpr == 1 { + cleanMetrics.gdprEnforced = true + parsedConsent, err := vendorconsent.ParseString(consent) + if err == nil { + cleanMetrics.gdprTcfVersion = int(parsedConsent.Version()) + } + } // bidder level privacy policies for bidder, bidReq := range requestsByBidder { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 4dad3f54648..e50d0f777f0 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -80,7 +80,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - reqByBidders, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -120,7 +120,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { }, } - results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) @@ -182,7 +182,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) diff --git a/go.sum b/go.sum index 5d941b89e90..35b2b76591d 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtW github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= +github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= @@ -126,6 +128,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index 4e249785ba6..3d105dead44 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -195,6 +195,13 @@ func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) { } } +// RecordTCFReq across all engines +func (me *MultiMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) { + for _, thisME := range *me { + thisME.RecordTCFReq(version) + } +} + // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) type DummyMetricsEngine struct{} @@ -273,3 +280,7 @@ func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType p // RecordTimeoutNotice as a noop func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { } + +// RecordReq as a noop +func (me *DummyMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) { +} diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index 1ced4d57269..73eb30a1504 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -48,9 +48,13 @@ type Metrics struct { ImpsTypeAudio metrics.Meter ImpsTypeNative metrics.Meter + // Notification timeout metrics TimeoutNotificationSuccess metrics.Meter TimeoutNotificationFailure metrics.Meter + // TCF adaption metrics + TCFReqVersion map[TCFVersionValue]metrics.Meter + AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics @@ -137,6 +141,8 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa TimeoutNotificationSuccess: blankMeter, TimeoutNotificationFailure: blankMeter, + TCFReqVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())), + AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), MetricsDisabled: disableMetrics, @@ -154,6 +160,15 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa } } + for _, c := range CacheResults() { + newMetrics.StoredReqCacheMeter[c] = blankMeter + newMetrics.StoredImpCacheMeter[c] = blankMeter + } + + for _, v := range TCFVersions() { + newMetrics.TCFReqVersion[v] = blankMeter + } + //to minimize memory usage, queuedTimeout metric is now supported for video endpoint only //boolean value represents 2 general request statuses: accepted and rejected newMetrics.RequestsQueueTimer["video"] = make(map[bool]metrics.Timer) @@ -218,6 +233,11 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry) newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry) + + for _, version := range TCFVersions() { + newMetrics.TCFReqVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry) + } + return newMetrics } @@ -562,6 +582,16 @@ func (me *Metrics) RecordTimeoutNotice(success bool) { return } +func (me *Metrics) RecordTCFReq(version TCFVersionValue) { + met, ok := me.TCFReqVersion[version] + if ok { + met.Mark(1) + } else { + me.TCFReqVersion[TCFVersionErr].Mark(1) + } + return +} + func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) { met, ok := meters[bidder] if ok { diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index 25f75e77758..6d9eaf9f0e9 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -56,6 +56,10 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess) ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure) + ensureContains(t, registry, "privacy.request.tcf.v1", m.TCFReqVersion[TCFVersionV1]) + ensureContains(t, registry, "privacy.request.tcf.v2", m.TCFReqVersion[TCFVersionV2]) + ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqVersion[TCFVersionErr]) + } func TestRecordBidType(t *testing.T) { diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index e65ba313338..0e94fe71e90 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -248,6 +248,35 @@ func RequestActions() []RequestAction { } } +// TCFVersionValue : The possible values for TCF versions +type TCFVersionValue string + +const ( + TCFVersionErr TCFVersionValue = "err" + TCFVersionV1 TCFVersionValue = "v1" + TCFVersionV2 TCFVersionValue = "v2" +) + +// TCFVersions rtuens the possible values for the TCF version +func TCFVersions() []TCFVersionValue { + return []TCFVersionValue{ + TCFVersionErr, + TCFVersionV1, + TCFVersionV2, + } +} + +// TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue +func TCFVersionToValue(version int) TCFVersionValue { + switch { + case version == 1: + return TCFVersionV1 + case version == 2: + return TCFVersionV2 + } + return TCFVersionErr +} + // MetricsEngine is a generic interface to record PBS metrics into the desired backend // The first three metrics function fire off once per incoming request, so total metrics // will equal the total number of incoming requests. The remaining 5 fire off per outgoing @@ -276,4 +305,5 @@ type MetricsEngine interface { RecordPrebidCacheRequestTime(success bool, length time.Duration) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) RecordTimeoutNotice(sucess bool) + RecordTCFReq(version TCFVersionValue) } diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index 482cbf24fae..a6d36a72401 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -106,3 +106,8 @@ func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType Re func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) { me.Called(success) } + +// RecordTCFReq mock +func (me *MetricsEngineMock) RecordTCFReq(version TCFVersionValue) { + me.Called(version) +} diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go index 11e6bdc14d8..19f4f225af9 100644 --- a/pbsmetrics/prometheus/preload.go +++ b/pbsmetrics/prometheus/preload.go @@ -99,6 +99,11 @@ func preloadLabelValues(m *Metrics) { requestTypeLabel: {string(pbsmetrics.ReqTypeVideo)}, requestStatusLabel: {requestSuccessLabel, requestRejectLabel}, }) + + preloadLabelValuesForCounter(m.tcfVersion, map[string][]string{ + versionLabel: tcfVersionsAsString(), + sourceLabel: {string(sourceRequest)}, + }) } func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index e385b044981..bf854746fd2 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -28,7 +28,8 @@ type Metrics struct { requestsWithoutCookie *prometheus.CounterVec storedImpressionsCacheResult *prometheus.CounterVec storedRequestCacheResult *prometheus.CounterVec - timeout_notifications *prometheus.CounterVec + timeoutNotifications *prometheus.CounterVec + tcfVersion *prometheus.CounterVec // Adapter Metrics adapterBids *prometheus.CounterVec @@ -63,6 +64,7 @@ const ( requestStatusLabel = "request_status" requestTypeLabel = "request_type" successLabel = "success" + versionLabel = "version" ) const ( @@ -85,6 +87,11 @@ const ( requestFailed = "failed" ) +const ( + sourceLabel = "source" + sourceRequest = "request" +) + // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. func NewMetrics(cfg config.PrometheusMetrics) *Metrics { requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} @@ -153,11 +160,16 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of stored request cache requests attempts by hits or miss.", []string{cacheResultLabel}) - metrics.timeout_notifications = newCounter(cfg, metrics.Registry, + metrics.timeoutNotifications = newCounter(cfg, metrics.Registry, "timeout_notification", "Count of timeout notifications triggered, and if they were successfully sent.", []string{successLabel}) + metrics.tcfVersion = newCounter(cfg, metrics.Registry, + "privacy_tcf", + "Count of TCF versions for requests where GDPR was enforced.", + []string{versionLabel, sourceLabel}) + metrics.adapterBids = newCounter(cfg, metrics.Registry, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -412,12 +424,19 @@ func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.Re func (m *Metrics) RecordTimeoutNotice(success bool) { if success { - m.timeout_notifications.With(prometheus.Labels{ + m.timeoutNotifications.With(prometheus.Labels{ successLabel: requestSuccessful, }).Inc() } else { - m.timeout_notifications.With(prometheus.Labels{ + m.timeoutNotifications.With(prometheus.Labels{ successLabel: requestFailed, }).Inc() } } + +func (m *Metrics) RecordTCFReq(version pbsmetrics.TCFVersionValue) { + m.tcfVersion.With(prometheus.Labels{ + versionLabel: string(version), + sourceLabel: sourceRequest, + }).Inc() +} diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index 24c50492139..03daff0d56b 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -930,13 +930,13 @@ func TestTimeoutNotifications(t *testing.T) { m.RecordTimeoutNotice(true) m.RecordTimeoutNotice(false) - assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeout_notifications, + assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeoutNotifications, float64(2), prometheus.Labels{ successLabel: requestSuccessful, }) - assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeout_notifications, + assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeoutNotifications, float64(1), prometheus.Labels{ successLabel: requestFailed, @@ -944,6 +944,36 @@ func TestTimeoutNotifications(t *testing.T) { } +func TestTCFMetrics(t *testing.T) { + m := createMetricsForTesting() + + m.RecordTCFReq(pbsmetrics.TCFVersionToValue(0)) + m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1)) + m.RecordTCFReq(pbsmetrics.TCFVersionToValue(2)) + m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1)) + + assertCounterVecValue(t, "", "privacy_tcf:err", m.tcfVersion, + float64(1), + prometheus.Labels{ + versionLabel: "err", + sourceLabel: sourceRequest, + }) + + assertCounterVecValue(t, "", "privacy_tcf:v1", m.tcfVersion, + float64(2), + prometheus.Labels{ + versionLabel: "v1", + sourceLabel: sourceRequest, + }) + + assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfVersion, + float64(1), + prometheus.Labels{ + versionLabel: "v2", + sourceLabel: sourceRequest, + }) +} + func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { m := dto.Metric{} counter.Write(&m) diff --git a/pbsmetrics/prometheus/type_conversion.go b/pbsmetrics/prometheus/type_conversion.go index 8294ede0617..55a7092ed6d 100644 --- a/pbsmetrics/prometheus/type_conversion.go +++ b/pbsmetrics/prometheus/type_conversion.go @@ -76,3 +76,12 @@ func requestTypesAsString() []string { } return valuesAsString } + +func tcfVersionsAsString() []string { + values := pbsmetrics.TCFVersions() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} From 1d276d5d9ad24dddee29d2cd5dc709645e572eb5 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:19:58 -0400 Subject: [PATCH 133/603] =?UTF-8?q?Fall=20back=20to=20constant=20rates=20w?= =?UTF-8?q?hen=20the=20currency=20rates=20endpoint=20i=E2=80=A6=20(#1364)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 2 + currencies/rate_converter.go | 77 ++++++--- currencies/rate_converter_test.go | 265 +++++++++++++++++++++--------- exchange/bidder_test.go | 2 + main.go | 4 +- 5 files changed, 248 insertions(+), 102 deletions(-) diff --git a/config/config.go b/config/config.go index bb2b909f5de..cc1d4a0ab4e 100755 --- a/config/config.go +++ b/config/config.go @@ -215,6 +215,7 @@ type Analytics struct { type CurrencyConverter struct { FetchURL string `mapstructure:"fetch_url"` FetchIntervalSeconds int `mapstructure:"fetch_interval_seconds"` + StaleRatesSeconds int `mapstructure:"stale_rates_seconds"` } func (cfg *CurrencyConverter) validate(errs configErrors) configErrors { @@ -866,6 +867,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("lmt.enforce", true) v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // fetch currency rates every 30 minutes + v.SetDefault("currency_converter.stale_rates_seconds", 0) v.SetDefault("default_request.type", "") v.SetDefault("default_request.file.name", "") v.SetDefault("default_request.alias_info", false) diff --git a/currencies/rate_converter.go b/currencies/rate_converter.go index 6c6ed172652..d22f347b17c 100644 --- a/currencies/rate_converter.go +++ b/currencies/rate_converter.go @@ -12,14 +12,15 @@ import ( // RateConverter holds the currencies conversion rates dictionary type RateConverter struct { - httpClient httpClient - done chan bool - updateNotifier chan<- int - fetchingInterval time.Duration - syncSourceURL string - rates atomic.Value // Should only hold Rates struct - lastUpdated atomic.Value // Should only hold time.Time - constantRates Conversions + httpClient httpClient + done chan bool + updateNotifier chan<- int + fetchingInterval time.Duration + staleRatesThreshold time.Duration + syncSourceURL string + rates atomic.Value // Should only hold Rates struct + lastUpdated atomic.Value // Should only hold time.Time + constantRates Conversions } // NewRateConverter returns a new RateConverter @@ -27,11 +28,13 @@ func NewRateConverter( httpClient httpClient, syncSourceURL string, fetchingInterval time.Duration, + staleRatesThreshold time.Duration, ) *RateConverter { return NewRateConverterWithNotifier( httpClient, syncSourceURL, fetchingInterval, + staleRatesThreshold, nil, // no notifier channel specified, won't send any notifications ) } @@ -40,7 +43,7 @@ func NewRateConverter( // By default there will be no currencies conversions done. // `currencies.ConstantRate` will be used. func NewRateConverterDefault() *RateConverter { - return NewRateConverter(&http.Client{}, "", time.Duration(0)) + return NewRateConverter(&http.Client{}, "", time.Duration(0), time.Duration(0)) } // NewRateConverterWithNotifier returns a new RateConverter @@ -51,22 +54,24 @@ func NewRateConverterWithNotifier( httpClient httpClient, syncSourceURL string, fetchingInterval time.Duration, + staleRatesThreshold time.Duration, updateNotifier chan<- int, ) *RateConverter { rc := &RateConverter{ - httpClient: httpClient, - done: make(chan bool), - updateNotifier: updateNotifier, - fetchingInterval: fetchingInterval, - syncSourceURL: syncSourceURL, - rates: atomic.Value{}, - lastUpdated: atomic.Value{}, + httpClient: httpClient, + done: make(chan bool), + updateNotifier: updateNotifier, + fetchingInterval: fetchingInterval, + staleRatesThreshold: staleRatesThreshold, + syncSourceURL: syncSourceURL, + rates: atomic.Value{}, + lastUpdated: atomic.Value{}, + constantRates: NewConstantRates(), } // In case host do not want to support currency lookup // we just stop here and do nothing if rc.fetchingInterval == time.Duration(0) { - rc.constantRates = NewConstantRates() return rc } @@ -111,7 +116,12 @@ func (rc *RateConverter) Update() error { rc.rates.Store(rates) rc.lastUpdated.Store(time.Now()) } else { - glog.Errorf("Error updating conversion rates: %v", err) + if rc.CheckStaleRates() { + rc.ClearRates() + glog.Errorf("Error updating conversion rates, falling back to constant rates: %v", err) + } else { + glog.Errorf("Error updating conversion rates: %v", err) + } } return err @@ -160,14 +170,33 @@ func (rc *RateConverter) LastUpdated() time.Time { // Rates returns current conversions rates func (rc *RateConverter) Rates() Conversions { - if rc.constantRates != nil { - // Converter is not active, returning the constant rates - return rc.constantRates - } - if rates := rc.rates.Load(); rates != nil { + // atomic.Value field rates is an empty interface and will be of type *Rates the first time rates are stored + // or nil if the rates have never been stored + if rates := rc.rates.Load(); rates != (*Rates)(nil) && rates != nil { return rates.(*Rates) } - return nil + return rc.constantRates +} + +// ClearRates sets the rates to nil +func (rc *RateConverter) ClearRates() { + // atomic.Value field rates must be of type *Rates so we cast nil to that type + rc.rates.Store((*Rates)(nil)) +} + +// CheckStaleRates checks if loaded third party conversion rates are stale +func (rc *RateConverter) CheckStaleRates() bool { + if rc.staleRatesThreshold <= 0 { + return false + } + currentTime := time.Now().UTC() + if lastUpdated := rc.lastUpdated.Load(); lastUpdated != nil { + delta := currentTime.Sub(lastUpdated.(time.Time).UTC()) + if delta.Seconds() > rc.staleRatesThreshold.Seconds() { + return true + } + } + return false } // GetInfo returns setup information about the converter diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go index cb5e2a0be54..d717d1a3f9c 100644 --- a/currencies/rate_converter_test.go +++ b/currencies/rate_converter_test.go @@ -13,6 +13,20 @@ import ( "github.com/stretchr/testify/assert" ) +func getMockRates() []byte { + return []byte(`{ + "dataAsOf":"2018-09-12", + "conversions":{ + "USD":{ + "GBP":0.77208 + }, + "GBP":{ + "USD":1.2952 + } + } + }`) +} + func TestFetch_Success(t *testing.T) { // Setup: @@ -21,19 +35,7 @@ func TestFetch_Success(t *testing.T) { func(rw http.ResponseWriter, req *http.Request) { calledURLs = append(calledURLs, req.RequestURI) rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - }`, - )) + rw.Write([]byte(getMockRates())) }), ) @@ -57,6 +59,7 @@ func TestFetch_Success(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: @@ -87,12 +90,13 @@ func TestFetch_Fail404(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -114,12 +118,13 @@ func TestFetch_FailErrorHttpClient(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -132,11 +137,12 @@ func TestFetch_FailBadSyncURL(t *testing.T) { &http.Client{}, "justaweirdurl", time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -172,12 +178,13 @@ func TestFetch_FailBadJSON(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -200,12 +207,13 @@ func TestFetch_InvalidRemoteResponseContent(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(24)*time.Hour, + time.Duration(24)*time.Hour, ) // Verify: assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -215,19 +223,7 @@ func TestInit(t *testing.T) { mockedHttpServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - }`, - )) + rw.Write([]byte(getMockRates())) }), ) @@ -239,6 +235,7 @@ func TestInit(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(100)*time.Millisecond, + time.Duration(24)*time.Hour, ticks, ) @@ -266,10 +263,8 @@ func TestInit(t *testing.T) { assert.False(t, intervalDiff > float64(errorMargin*100), "Interval between ticks should be: %d but was: %d", expectedIntervalDuration, intervalDuration) } - assert.NotNil(t, currencyConverter.Rates(), "Rates shouldn't be nil") assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated should be set") - rates := currencyConverter.Rates() - assert.Equal(t, expectedRates, rates, "Conversions.Rates weren't the expected ones") + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") if ticksCount == expectedTicks { @@ -287,19 +282,7 @@ func TestStop(t *testing.T) { func(rw http.ResponseWriter, req *http.Request) { calledURLs = append(calledURLs, req.RequestURI) rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - }`, - )) + rw.Write([]byte(getMockRates())) }), ) @@ -310,6 +293,7 @@ func TestStop(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(100)*time.Millisecond, + time.Duration(24)*time.Hour, ticks, ) @@ -337,19 +321,7 @@ func TestInitWithZeroDuration(t *testing.T) { func(rw http.ResponseWriter, req *http.Request) { calledURLs = append(calledURLs, req.RequestURI) rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - }`, - )) + rw.Write([]byte(getMockRates())) }), ) @@ -358,6 +330,7 @@ func TestInitWithZeroDuration(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(0), + time.Duration(24)*time.Hour, ) // Verify: @@ -366,8 +339,7 @@ func TestInitWithZeroDuration(t *testing.T) { assert.Equal(t, 0, len(calledURLs), "sync URL shouldn't have been called but was called %d times", 0, len(calledURLs)) assert.Equal(t, (time.Time{}), currencyConverter.LastUpdated(), "LastUpdated() shouldn't be set") - _, ok := currencyConverter.Rates().(*currencies.ConstantRates) - assert.True(t, ok, "Rates should be type of `currencies.ConstantRates`") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") } @@ -393,19 +365,7 @@ func TestRates(t *testing.T) { mockedHttpServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - }`, - )) + rw.Write([]byte(getMockRates())) }), ) @@ -415,6 +375,7 @@ func TestRates(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(100)*time.Millisecond, + time.Duration(24)*time.Hour, ticks, ) rates := currencyConverter.Rates() @@ -456,12 +417,161 @@ func TestRates_EmptyRates(t *testing.T) { &http.Client{}, mockedHttpServer.URL, time.Duration(100)*time.Millisecond, + time.Duration(24)*time.Hour, ) defer currencyConverter.StopPeriodicFetching() - rates := currencyConverter.Rates() // Verify: - assert.Nil(t, rates, "rates should be nil") + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") +} + +func TestSelectRatesBasedOnStaleness(t *testing.T) { + calledURLs := []string{} + callCnt := 0 + mockedHttpServer := httptest.NewServer(http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + calledURLs = append(calledURLs, req.RequestURI) + if callCnt == 0 || callCnt >= 5 { + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(getMockRates())) + } else { + rw.WriteHeader(http.StatusNotFound) + } + callCnt++ + }), + ) + + defer mockedHttpServer.Close() + + expectedRates := ¤cies.Rates{ + DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), + Conversions: map[string]map[string]float64{ + "USD": { + "GBP": 0.77208, + }, + "GBP": { + "USD": 1.2952, + }, + }, + } + + // Execute: + currencyConverter := currencies.NewRateConverter( + &http.Client{}, + mockedHttpServer.URL, + time.Duration(100)*time.Millisecond, + time.Duration(200)*time.Millisecond, + ) + + // Verify: + // Rates are valid at t=0, then invalid for 500ms before being valid again + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + + time.Sleep(100 * time.Millisecond) + // Rates have been invalid for ~100ms, rates not stale yet + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + + time.Sleep(200 * time.Millisecond) + // Rates have been invalid for ~300ms, rates are stale + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + + time.Sleep(300 * time.Millisecond) + // Rates have been valid again for ~100ms + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") +} + +func TestUseConstantRatesUntilFetchIsSuccessful(t *testing.T) { + callCnt := 0 + mockedHttpServer := httptest.NewServer(http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + if callCnt >= 5 { + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(getMockRates())) + } else { + rw.WriteHeader(http.StatusNotFound) + } + callCnt++ + }), + ) + + defer mockedHttpServer.Close() + + expectedRates := ¤cies.Rates{ + DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), + Conversions: map[string]map[string]float64{ + "USD": { + "GBP": 0.77208, + }, + "GBP": { + "USD": 1.2952, + }, + }, + } + + // Execute: + currencyConverter := currencies.NewRateConverter( + &http.Client{}, + mockedHttpServer.URL, + time.Duration(100)*time.Millisecond, + time.Duration(1)*time.Second, + ) + + // Verify: + // Rates are invalid at t=0 and remain invalid until 500ms have elapsed + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + + time.Sleep(400 * time.Millisecond) + // Rates have been invalid for ~400ms + assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + + time.Sleep(200 * time.Millisecond) + // Rates have been valid for ~100ms + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") +} + +func TestRatesAreNeverStale(t *testing.T) { + callCnt := 0 + mockedHttpServer := httptest.NewServer(http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + if callCnt == 0 { + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(getMockRates())) + } else { + rw.WriteHeader(http.StatusNotFound) + } + callCnt++ + }), + ) + + defer mockedHttpServer.Close() + + expectedRates := ¤cies.Rates{ + DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), + Conversions: map[string]map[string]float64{ + "USD": { + "GBP": 0.77208, + }, + "GBP": { + "USD": 1.2952, + }, + }, + } + + // Execute: + currencyConverter := currencies.NewRateConverter( + &http.Client{}, + mockedHttpServer.URL, + time.Duration(100)*time.Millisecond, + time.Duration(0)*time.Millisecond, + ) + + // Verify: + // Rates are valid at t=0 and are then invalid at 100ms + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + + time.Sleep(500 * time.Millisecond) + // Rates have been invalid for ~400ms + assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") } func TestRace(t *testing.T) { @@ -495,6 +605,7 @@ func TestRace(t *testing.T) { mockedHttpClient, "currency.fake.com", time.Duration(10)*time.Millisecond, + time.Duration(24)*time.Hour, ) defer currencyConverter.StopPeriodicFetching() diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index fff397f0084..b776715adaf 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -517,6 +517,7 @@ func TestMultiCurrencies(t *testing.T) { &http.Client{}, mockedHTTPServer.URL, time.Duration(10)*time.Second, + time.Duration(24)*time.Hour, ) seatBid, errs := bidder.requestBid( context.Background(), @@ -831,6 +832,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { &http.Client{}, mockedHTTPServer.URL, time.Duration(10)*time.Second, + time.Duration(24)*time.Hour, ) seatBid, errs := bidder.requestBid( context.Background(), diff --git a/main.go b/main.go index d6ba430f059..9a835f42a4c 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,9 @@ func loadConfig() (*config.Configuration, error) { func serve(revision string, cfg *config.Configuration) error { fetchingInterval := time.Duration(cfg.CurrencyConverter.FetchIntervalSeconds) * time.Second - currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, fetchingInterval) + staleRatesThreshold := time.Duration(cfg.CurrencyConverter.StaleRatesSeconds) * time.Second + currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, + fetchingInterval, staleRatesThreshold) r, err := router.New(cfg, currencyConverter) if err != nil { From 33f36b6be002e8993fe66be8985c6e95938512fb Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Mon, 6 Jul 2020 17:12:07 +0300 Subject: [PATCH 134/603] TheMediaGrid: added app type support (#1377) --- static/bidder-info/grid.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index 9594830c0d0..325421a2c05 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -1,7 +1,11 @@ maintainer: email: "grid-tech@themediagrid.com" capabilities: - site: + app: mediaTypes: - banner - video + site: + mediaTypes: + - banner + - video \ No newline at end of file From 0f2dc5f7bec5a13a65792dbc33c4fa759f2b6899 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Wed, 8 Jul 2020 01:39:27 +0300 Subject: [PATCH 135/603] user.ext.eids support in adform adapter (#1381) --- adapters/adform/adform.go | 31 +++++++++++++++++++++++++++++++ adapters/adform/adform_test.go | 31 ++++++++++++++++++++++++++++++- openrtb_ext/user.go | 5 +++-- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 3aeea62ebde..69f1c12f073 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -42,6 +42,7 @@ type adformRequest struct { consent string digitrust *adformDigitrust currency string + eids string } type adformDigitrust struct { @@ -279,6 +280,9 @@ func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { parameters.Add("gdpr", r.gdprApplies) parameters.Add("gdpr_consent", r.consent) + if r.eids != "" { + parameters.Add("eids", r.eids) + } URL := *a.URL URL.RawQuery = parameters.Encode() @@ -465,6 +469,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro } } + eids := "" consent := "" var digitrustData *openrtb_ext.ExtUserDigiTrust if request.User != nil { @@ -472,6 +477,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { consent = extUser.Consent digitrustData = extUser.DigiTrust + eids = encodeEids(extUser.Eids) } } @@ -513,9 +519,34 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro consent: consent, digitrust: digitrust, currency: requestCurrency, + eids: eids, }, errors } +func encodeEids(eids []openrtb_ext.ExtUserEid) string { + if eids == nil { + return "" + } + + eidsMap := make(map[string]map[string][]int) + for _, eid := range eids { + _, ok := eidsMap[eid.Source] + if !ok { + eidsMap[eid.Source] = make(map[string][]int) + } + for _, uid := range eid.Uids { + eidsMap[eid.Source][uid.ID] = append(eidsMap[eid.Source][uid.ID], uid.Atype) + } + } + + encodedEids := "" + if eidsString, err := json.Marshal(eidsMap); err == nil { + encodedEids = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(eidsString) + } + + return encodedEids +} + func getIPSafely(device *openrtb.Device) string { if device == nil { return "" diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 63646f5f7f5..2fca7d1722d 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -480,7 +480,33 @@ func getUserExt() []byte { KeyV: 1, Pref: 0, } + + eids := []openrtb_ext.ExtUserEid{ + { + Source: "test.com", + Uids: []openrtb_ext.ExtUserEidUid{ + { + ID: "some_user_id", + Atype: 1, + }, + { + ID: "other_user_id", + }, + }, + }, + { + Source: "test2.org", + Uids: []openrtb_ext.ExtUserEidUid{ + { + ID: "other_user_id", + Atype: 2, + }, + }, + }, + } + userExt := openrtb_ext.ExtUser{ + Eids: eids, Consent: "abc", DigiTrust: &digitrust, } @@ -519,13 +545,16 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo } var midsWithCurrency = "" + var queryString = "" if isOpenRtb { midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9RVVS&bWlkPTMyMzQ2JnJjdXI9RVVS" + queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency } else { midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9VVNE&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter + queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency } - if ok, err := equal("CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&"+midsWithCurrency, r.URL.RawQuery, "Query string"); !ok { + if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { return err } if ok, err := equal("application/json;charset=utf-8", r.Header.Get("Content-Type"), "Content type"); !ok { diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index 520d73a6ed1..b83f82330db 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -43,6 +43,7 @@ type ExtUserEid struct { // ExtUserEidUid defines the contract for bidrequest.user.ext.eids[i].uids[j] type ExtUserEidUid struct { - ID string `json:"id"` - Ext json.RawMessage `json:"ext,omitempty"` + ID string `json:"id"` + Atype int `json:"atype,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } From 034928ebb64b0aecab6c935188027fd45614f2a8 Mon Sep 17 00:00:00 2001 From: logicad Date: Fri, 10 Jul 2020 01:23:53 +0900 Subject: [PATCH 136/603] Add Logicad adapter (#1382) --- adapters/logicad/logicad.go | 155 ++++++++++++++++++ adapters/logicad/logicad_test.go | 10 ++ .../logicad/logicadtest/exemplary/banner.json | 92 +++++++++++ .../logicadtest/params/race/banner.json | 3 + .../logicadtest/supplemental/checkImp.json | 15 ++ .../logicad/logicadtest/supplemental/ext.json | 31 ++++ .../logicadtest/supplemental/missingtid.json | 33 ++++ .../supplemental/multiImpSameTid.json | 112 +++++++++++++ .../supplemental/responseCode.json | 72 ++++++++ .../supplemental/responseNoBid.json | 66 ++++++++ .../logicadtest/supplemental/responsebid.json | 73 +++++++++ .../logicadtest/supplemental/site.json | 98 +++++++++++ adapters/logicad/params_test.go | 45 +++++ adapters/logicad/usersync.go | 12 ++ adapters/logicad/usersync_test.go | 31 ++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_logicad.go | 5 + static/bidder-info/logicad.yaml | 10 ++ static/bidder-params/logicad.json | 13 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 23 files changed, 885 insertions(+) create mode 100644 adapters/logicad/logicad.go create mode 100644 adapters/logicad/logicad_test.go create mode 100644 adapters/logicad/logicadtest/exemplary/banner.json create mode 100644 adapters/logicad/logicadtest/params/race/banner.json create mode 100644 adapters/logicad/logicadtest/supplemental/checkImp.json create mode 100644 adapters/logicad/logicadtest/supplemental/ext.json create mode 100644 adapters/logicad/logicadtest/supplemental/missingtid.json create mode 100644 adapters/logicad/logicadtest/supplemental/multiImpSameTid.json create mode 100644 adapters/logicad/logicadtest/supplemental/responseCode.json create mode 100644 adapters/logicad/logicadtest/supplemental/responseNoBid.json create mode 100644 adapters/logicad/logicadtest/supplemental/responsebid.json create mode 100644 adapters/logicad/logicadtest/supplemental/site.json create mode 100644 adapters/logicad/params_test.go create mode 100644 adapters/logicad/usersync.go create mode 100644 adapters/logicad/usersync_test.go create mode 100644 openrtb_ext/imp_logicad.go create mode 100644 static/bidder-info/logicad.yaml create mode 100644 static/bidder-params/logicad.json diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go new file mode 100644 index 00000000000..e757705a7bd --- /dev/null +++ b/adapters/logicad/logicad.go @@ -0,0 +1,155 @@ +package logicad + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type LogicadAdapter struct { + endpoint string +} + +func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{Message: "No impression in the bid request"}} + } + + pub2impressions, imps, errs := getImpressionsInfo(request.Imp) + if len(pub2impressions) == 0 || len(imps) == 0 { + return nil, errs + } + + result := make([]*adapters.RequestData, 0, len(pub2impressions)) + for k, imps := range pub2impressions { + bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) + if err != nil { + errs = append(errs, err) + } else { + result = append(result, bidRequest) + } + } + return result, errs +} + +func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb.Imp, []openrtb.Imp, []error) { + errors := make([]error, 0, len(imps)) + resImps := make([]openrtb.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLogicad][]openrtb.Imp) + + for _, imp := range imps { + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + if err := validateImpression(&impExt); err != nil { + errors = append(errors, err) + continue + } + + if res[impExt] == nil { + res[impExt] = make([]openrtb.Imp, 0) + } + res[impExt] = append(res[impExt], imp) + resImps = append(resImps, imp) + } + return res, resImps, errors +} + +func validateImpression(impExt *openrtb_ext.ExtImpLogicad) error { + if impExt.Tid == "" { + return &errortypes.BadInput{Message: "No tid value provided"} + } + return nil +} + +func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) { + var bidderExt adapters.ExtImpBidder + var logicadExt openrtb_ext.ExtImpLogicad + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return logicadExt, &errortypes.BadInput{ + Message: err.Error(), + } + } + if err := json.Unmarshal(bidderExt.Bidder, &logicadExt); err != nil { + return logicadExt, &errortypes.BadInput{ + Message: err.Error(), + } + } + return logicadExt, nil +} + +func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) (*adapters.RequestData, error) { + newBidRequest := createBidRequest(prebidBidRequest, params, imps) + reqJSON, err := json.Marshal(newBidRequest) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: reqJSON, + Headers: headers}, nil +} + +func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) *openrtb.BidRequest { + bidRequest := *prebidBidRequest + bidRequest.Imp = imps + for idx := range bidRequest.Imp { + imp := &bidRequest.Imp[idx] + imp.TagID = params.Tid + imp.Ext = nil + } + return &bidRequest +} + +//MakeBids translates Logicad bid response to prebid-server specific format +func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode != http.StatusOK { + msg := fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + msg := fmt.Sprintf("Bad server response: %d", err) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + if len(bidResp.SeatBid) != 1 { + msg := fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid)) + + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + return bidResponse, nil +} + +func NewLogicadBidder(endpoint string) adapters.Bidder { + return &LogicadAdapter{ + endpoint: endpoint, + } +} diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go new file mode 100644 index 00000000000..adf20e4ed33 --- /dev/null +++ b/adapters/logicad/logicad_test.go @@ -0,0 +1,10 @@ +package logicad + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "logicadtest", NewLogicadBidder("https://localhost/adrequest/prebidserver")) +} diff --git a/adapters/logicad/logicadtest/exemplary/banner.json b/adapters/logicad/logicadtest/exemplary/banner.json new file mode 100644 index 00000000000..f782cc2b9f8 --- /dev/null +++ b/adapters/logicad/logicadtest/exemplary/banner.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "tagid": "testtid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/logicad/logicadtest/params/race/banner.json b/adapters/logicad/logicadtest/params/race/banner.json new file mode 100644 index 00000000000..7cb3de5a1ef --- /dev/null +++ b/adapters/logicad/logicadtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "tid": "testtid" +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/checkImp.json b/adapters/logicad/logicadtest/supplemental/checkImp.json new file mode 100644 index 00000000000..62c6e3e8f9e --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/checkImp.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test", + "domain": "test.com" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/ext.json b/adapters/logicad/logicadtest/supplemental/ext.json new file mode 100644 index 00000000000..ad35892086b --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/ext.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "tid": "testtid" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/missingtid.json b/adapters/logicad/logicadtest/supplemental/missingtid.json new file mode 100644 index 00000000000..5ed84cef65e --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/missingtid.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No tid value provided", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json b/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json new file mode 100644 index 00000000000..848733cdf35 --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + }, + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "tagid": "testtid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "testimpid", + "tagid": "testtid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/logicad/logicadtest/supplemental/responseCode.json b/adapters/logicad/logicadtest/supplemental/responseCode.json new file mode 100644 index 00000000000..471993ad8f2 --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/responseCode.json @@ -0,0 +1,72 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "id": "testimpid", + "tagid": "testtid" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "body": { + "seatbid": [] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/responseNoBid.json b/adapters/logicad/logicadtest/supplemental/responseNoBid.json new file mode 100644 index 00000000000..6ddab2ab6bd --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/responseNoBid.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ], + "site": { + "id": "test" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "tagid": "testtid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + } + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/logicad/logicadtest/supplemental/responsebid.json b/adapters/logicad/logicadtest/supplemental/responsebid.json new file mode 100644 index 00000000000..59d1c2ac21e --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/responsebid.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "id": "testimpid", + "tagid": "testtid" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/logicadtest/supplemental/site.json b/adapters/logicad/logicadtest/supplemental/site.json new file mode 100644 index 00000000000..c747413f91c --- /dev/null +++ b/adapters/logicad/logicadtest/supplemental/site.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "tid": "testtid" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/adrequest/prebidserver", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 320, + "h": 50 + } + ] + }, + "id": "testimpid", + "tagid": "testtid" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "123", + "adid": "456", + "price": 0.12, + "id": "testid", + "impid": "testimpid", + "cid": "789" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/logicad/params_test.go b/adapters/logicad/params_test.go new file mode 100644 index 00000000000..eb34452811b --- /dev/null +++ b/adapters/logicad/params_test.go @@ -0,0 +1,45 @@ +package logicad + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderLogicad, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected LunaMedia params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderLogicad, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"tid": "testtid"}`, +} + +var invalidParams = []string{ + `nil`, + ``, + `[]`, + `true`, + `{"tid": 42}`, +} diff --git a/adapters/logicad/usersync.go b/adapters/logicad/usersync.go new file mode 100644 index 00000000000..d26a197b0a1 --- /dev/null +++ b/adapters/logicad/usersync.go @@ -0,0 +1,12 @@ +package logicad + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewLogicadSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("logicad", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/logicad/usersync_test.go b/adapters/logicad/usersync_test.go new file mode 100644 index 00000000000..89d6207d348 --- /dev/null +++ b/adapters/logicad/usersync_test.go @@ -0,0 +1,31 @@ +package logicad + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestLogicadSyncer(t *testing.T) { + syncURL := "https://localhost/cookiesender?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewLogicadSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "A", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://localhost/cookiesender?r=true&gdpr=1&gdpr_consent=A&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index cc1d4a0ab4e..65ad352c938 100755 --- a/config/config.go +++ b/config/config.go @@ -597,6 +597,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=184932&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") @@ -808,6 +809,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest") v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") + v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 1f62d232233..53607ac57d8 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -48,6 +48,7 @@ import ( "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" @@ -133,6 +134,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), openrtb_ext.BidderLunaMedia: lunamedia.NewLunaMediaBidder(cfg.Adapters[string(openrtb_ext.BidderLunaMedia)].Endpoint), + openrtb_ext.BidderLogicad: logicad.NewLogicadBidder(cfg.Adapters[string(openrtb_ext.BidderLogicad)].Endpoint), openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), openrtb_ext.BidderMobileFuse: mobilefuse.NewMobileFuseBidder(cfg.Adapters[string(openrtb_ext.BidderMobileFuse)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 49d7b09d671..62fb9750616 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -64,6 +64,7 @@ const ( BidderKubient BidderName = "kubient" BidderLifestreet BidderName = "lifestreet" BidderLockerDome BidderName = "lockerdome" + BidderLogicad BidderName = "logicad" BidderLunaMedia BidderName = "lunamedia" BidderMarsmedia BidderName = "marsmedia" BidderMgid BidderName = "mgid" @@ -145,6 +146,7 @@ var BidderMap = map[string]BidderName{ "kubient": BidderKubient, "lifestreet": BidderLifestreet, "lockerdome": BidderLockerDome, + "logicad": BidderLogicad, "lunamedia": BidderLunaMedia, "marsmedia": BidderMarsmedia, "mgid": BidderMgid, diff --git a/openrtb_ext/imp_logicad.go b/openrtb_ext/imp_logicad.go new file mode 100644 index 00000000000..e4e3c3b091c --- /dev/null +++ b/openrtb_ext/imp_logicad.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpLogicad struct { + Tid string `json:"tid"` +} diff --git a/static/bidder-info/logicad.yaml b/static/bidder-info/logicad.yaml new file mode 100644 index 00000000000..c087516c061 --- /dev/null +++ b/static/bidder-info/logicad.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "prebid@so-netmedia.jp" +capabilities: + site: + mediaTypes: + - banner + app: + mediaTypes: + - banner + diff --git a/static/bidder-params/logicad.json b/static/bidder-params/logicad.json new file mode 100644 index 00000000000..2a892f91266 --- /dev/null +++ b/static/bidder-params/logicad.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Logicad Adapter Params", + "description": "A schema which validates params accepted by the Logicad adapter", + "type": "object", + "properties": { + "tid": { + "type": "string", + "description": "Logicad for Publishers placement ID" + } + }, + "required": ["tid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index f1f643afb74..89540ea205b 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -39,6 +39,7 @@ import ( "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" @@ -115,6 +116,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index b23541eaf8a..32ab2e730eb 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -48,6 +48,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, + string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, From 7c3521b2c8e1bec25341cb54072e8770becea820 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 13 Jul 2020 23:35:29 -0400 Subject: [PATCH 137/603] Fix Previous Merge Conflict (#1392) --- config/config.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 65ad352c938..f33dba69b60 100755 --- a/config/config.go +++ b/config/config.go @@ -764,12 +764,8 @@ func SetupViper(v *viper.Viper, filename string) { // Disabling adapters by default that require some specific config params. // If you're using one of these, make sure you check out the documentation (https://github.com/prebid/prebid-server/tree/master/docs/bidders) // for them and specify all the parameters they need for them to work correctly. - v.SetDefault("adapters.audiencenetwork.disabled", true) - v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") v.SetDefault("adapters.33across.partner_id", "") - v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2") - v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction") v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") @@ -787,6 +783,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs v.SetDefault("adapters.appnexus.platform_id", "5") + v.SetDefault("adapters.audiencenetwork.disabled", true) v.SetDefault("adapters.avocet.disabled", true) v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") @@ -796,6 +793,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") + v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") @@ -823,6 +821,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") + v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1") v.SetDefault("adapters.smartadserver.endpoint", "https://ssb.smartadserver.com") From bb2b03748ee9a7a83c09243762bfaf50b91dea77 Mon Sep 17 00:00:00 2001 From: Marsel Date: Wed, 15 Jul 2020 08:49:58 +0300 Subject: [PATCH 138/603] Kubient: Change default endpont address (#1398) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index f33dba69b60..5d538f38523 100755 --- a/config/config.go +++ b/config/config.go @@ -804,7 +804,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") - v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") + v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid") v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest") v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") From e6d159e71dd479338207f4eb7f7746b71a0e3d9d Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 15 Jul 2020 10:07:53 -0400 Subject: [PATCH 139/603] Add support for multiple root schain nodes (#1374) --- endpoints/openrtb2/auction.go | 9 ++ endpoints/openrtb2/auction_test.go | 47 ++++++++ exchange/utils.go | 94 ++++++++++++++- exchange/utils_test.go | 180 +++++++++++++++++++++++++++++ openrtb_ext/request.go | 43 ++++++- 5 files changed, 366 insertions(+), 7 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 20acc2aedd3..3fd2132143e 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -290,6 +290,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { if err := validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil { return []error{err} } + + if err := validateSChains(bidExt); err != nil { + return []error{err} + } } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { @@ -362,6 +366,11 @@ func validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases return nil } +func validateSChains(req *openrtb_ext.ExtRequest) error { + _, err := exchange.BidderToPrebidSChains(req) + return err +} + func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error { if imp.ID == "" { return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 97f0038a392..c697c206483 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1039,6 +1039,53 @@ func TestCCPAInvalid(t *testing.T) { assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } +func TestSChainInvalid(t *testing.T) { + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{}, + pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + ui := uint64(1) + req := openrtb.BidRequest{ + ID: "someID", + Imp: []openrtb.Imp{ + { + ID: "imp-ID", + Banner: &openrtb.Banner{ + W: &ui, + H: &ui, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), + }, + }, + Site: &openrtb.Site{ + ID: "myID", + }, + Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"abcd"}`), + }, + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + } + + errL := deps.validateRequest(&req) + + expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") + assert.ElementsMatch(t, errL, []error{expectedError}) +} + func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string diff --git a/exchange/utils.go b/exchange/utils.go index 96c00ec0e36..4de985eca40 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -28,6 +28,29 @@ type cleanMetrics struct { gdprTcfVersion int } +func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { + bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) + + if len(req.Prebid.SChains) == 0 { + return bidderToSChains, nil + } + + for _, schainWrapper := range req.Prebid.SChains { + if schainWrapper != nil && len(schainWrapper.Bidders) > 0 { + for _, bidder := range schainWrapper.Bidders { + if _, present := bidderToSChains[bidder]; present { + return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ + "it must contain no more than one per bidder.", bidder) + } else { + bidderToSChains[bidder] = &schainWrapper.SChain + } + } + } + } + + return bidderToSChains, nil +} + // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: // // 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder. @@ -103,12 +126,35 @@ func cleanOpenRTBRequests(ctx context.Context, return } -func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb.Imp, aliases map[string]string, usersyncs IdFetcher, blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) { +func splitBidRequest(req *openrtb.BidRequest, + impsByBidder map[string][]openrtb.Imp, + aliases map[string]string, + usersyncs IdFetcher, + blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, + labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) { + requestsByBidder := make(map[openrtb_ext.BidderName]*openrtb.BidRequest, len(impsByBidder)) explicitBuyerUIDs, err := extractBuyerUIDs(req.User) if err != nil { return nil, []error{err} } + + var requestExt openrtb_ext.ExtRequest + var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain + if len(req.Ext) > 0 { + err := json.Unmarshal(req.Ext, &requestExt) + if err != nil { + return nil, []error{err} + } + + sChainsByBidder, err = BidderToPrebidSChains(&requestExt) + if err != nil { + return nil, []error{err} + } + } else { + sChainsByBidder = make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) + } + for bidder, imps := range impsByBidder { reqCopy := *req coreBidder := resolveBidder(bidder, aliases) @@ -128,11 +174,57 @@ func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb. blabels[coreBidder].CookieFlag = pbsmetrics.CookieFlagYes } reqCopy.Imp = imps + + prepareSource(&reqCopy, bidder, sChainsByBidder) + prepareExt(&reqCopy, &requestExt) + requestsByBidder[openrtb_ext.BidderName(bidder)] = &reqCopy } return requestsByBidder, nil } +func prepareExt(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) { + if len(req.Ext) == 0 { + return + } + extCopy := *unpackedExt + extCopy.Prebid.SChains = nil + reqExt, err := json.Marshal(extCopy) + if err == nil { + req.Ext = reqExt + } +} + +func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { + const sChainWildCard = "*" + var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain + + wildCardSChain := sChainsByBidder[sChainWildCard] + bidderSChain := sChainsByBidder[bidder] + + // source should not be modified + if bidderSChain == nil && wildCardSChain == nil { + return + } + + if bidderSChain != nil { + selectedSChain = bidderSChain + } else { + selectedSChain = wildCardSChain + } + + // set source + var source openrtb.Source + schain := openrtb_ext.ExtRequestPrebidSChain{ + SChain: *selectedSChain, + } + sourceExt, err := json.Marshal(schain) + if err == nil { + source.Ext = sourceExt + req.Source = &source + } +} + // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. // This prevents a Bidder from using these values to figure out who else is involved in the Auction. func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index e50d0f777f0..6d66e816e7b 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -135,6 +135,92 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { } } +func TestCleanOpenRTBRequestsSChain(t *testing.T) { + testCases := []struct { + description string + inSourceExt json.RawMessage + inExt json.RawMessage + outSourceExt json.RawMessage + outExt json.RawMessage + hasError bool + }{ + { + description: "Empty root ext and source ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(``), + outSourceExt: json.RawMessage(``), + outExt: json.RawMessage(``), + hasError: false, + }, + { + description: "No schains in root ext and empty source ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[]}}`), + outSourceExt: json.RawMessage(``), + outExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, + }, + { + description: "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains", + inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + outExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, + }, + { + description: "Use schain for bidder in ext.prebid.schains", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, + }, + { + description: "Use wildcard schain in ext.prebid.schains", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, + }, + { + description: "Use schain for bidder in ext.prebid.schains instead of wildcard", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`), + hasError: false, + }, + { + description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains", + inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: nil, + outExt: nil, + hasError: true, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Source.Ext = test.inSourceExt + req.Ext = test.inExt + + results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{}) + result := results["appnexus"] + + if test.hasError == true { + assert.NotNil(t, errs) + assert.Nil(t, result) + } else { + assert.Nil(t, errs) + assert.Equal(t, test.outSourceExt, result.Source.Ext, test.description+":Source.Ext") + assert.Equal(t, test.outExt, result.Ext, test.description+":Ext") + } + } +} + func TestCleanOpenRTBRequestsLMT(t *testing.T) { var ( enabled int8 = 1 @@ -302,5 +388,99 @@ func TestRandomizeList(t *testing.T) { if len(adapters) != 1 { t.Errorf("RandomizeList, expected a list of 1, found %d", len(adapters)) } +} + +func TestBidderToPrebidChains(t *testing.T) { + input := openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + SChains: []*openrtb_ext.ExtRequestPrebidSChain{ + { + Bidders: []string{"Bidder1", "Bidder2"}, + SChain: openrtb_ext.ExtRequestPrebidSChainSChain{ + Complete: 1, + Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + { + ASI: "asi1", + SID: "sid1", + Name: "name1", + RID: "rid1", + Domain: "domain1", + HP: 1, + }, + { + ASI: "asi2", + SID: "sid2", + Name: "name2", + RID: "rid2", + Domain: "domain2", + HP: 2, + }, + }, + Ver: "version1", + }, + }, + { + Bidders: []string{"Bidder3", "Bidder4"}, + SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + }, + }, + }, + } + + output, err := BidderToPrebidSChains(&input) + + assert.Nil(t, err) + assert.Equal(t, len(output), 4) + assert.Same(t, output["Bidder1"], &input.Prebid.SChains[0].SChain) + assert.Same(t, output["Bidder2"], &input.Prebid.SChains[0].SChain) + assert.Same(t, output["Bidder3"], &input.Prebid.SChains[1].SChain) + assert.Same(t, output["Bidder4"], &input.Prebid.SChains[1].SChain) +} + +func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) { + input := openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + SChains: []*openrtb_ext.ExtRequestPrebidSChain{ + { + Bidders: []string{"Bidder1"}, + SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + }, + { + Bidders: []string{"Bidder1", "Bidder2"}, + SChain: openrtb_ext.ExtRequestPrebidSChainSChain{}, + }, + }, + }, + } + + output, err := BidderToPrebidSChains(&input) + + assert.NotNil(t, err) + assert.Nil(t, output) +} + +func TestBidderToPrebidChainsNilSChains(t *testing.T) { + input := openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + SChains: nil, + }, + } + + output, err := BidderToPrebidSChains(&input) + + assert.Nil(t, err) + assert.Equal(t, len(output), 0) +} + +func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) { + input := openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + SChains: []*openrtb_ext.ExtRequestPrebidSChain{}, + }, + } + + output, err := BidderToPrebidSChains(&input) + assert.Nil(t, err) + assert.Equal(t, len(output), 0) } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 25b5c881408..86388f60cf4 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -12,12 +12,43 @@ type ExtRequest struct { // ExtRequestPrebid defines the contract for bidrequest.ext.prebid type ExtRequestPrebid struct { - Aliases map[string]string `json:"aliases,omitempty"` - BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` - Cache *ExtRequestPrebidCache `json:"cache,omitempty"` - StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` - Targeting *ExtRequestTargeting `json:"targeting,omitempty"` - SupportDeals bool `json:"supportdeals,omitempty"` + Aliases map[string]string `json:"aliases,omitempty"` + BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` + Cache *ExtRequestPrebidCache `json:"cache,omitempty"` + SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"` + StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` + Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + SupportDeals bool `json:"supportdeals,omitempty"` +} + +// ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains +type ExtRequestPrebidSChain struct { + Bidders []string `json:"bidders,omitempty"` + SChain ExtRequestPrebidSChainSChain `json:"schain"` +} + +// ExtRequestPrebidSChainSChain defines the contract for bidrequest.ext.prebid.schains[i].schain +type ExtRequestPrebidSChainSChain struct { + Complete int `json:"complete"` + Nodes []*ExtRequestPrebidSChainSChainNode `json:"nodes"` + Ver string `json:"ver"` + Ext json.RawMessage `json:"ext,omitempty"` +} + +// ExtRequestPrebidSChainSChainNode defines the contract for bidrequest.ext.prebid.schains[i].schain[i].nodes +type ExtRequestPrebidSChainSChainNode struct { + ASI string `json:"asi"` + SID string `json:"sid"` + RID string `json:"rid,omitempty"` + Name string `json:"name,omitempty"` + Domain string `json:"domain,omitempty"` + HP int `json:"hp"` + Ext json.RawMessage `json:"ext,omitempty"` +} + +// SourceExt defines the contract for bidrequest.source.ext +type SourceExt struct { + SChain ExtRequestPrebidSChainSChain `json:"schain"` } // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache From e6fe57e058a0dd44273916d5c919829937ec13dc Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Wed, 15 Jul 2020 22:05:49 -0400 Subject: [PATCH 140/603] Update endpoint for latest release by districtm (#1401) Co-authored-by: steve-a-districtm --- adapters/dmx/dmx.go | 2 +- config/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 6b4f698d4b1..de33bd390e5 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -160,7 +160,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte } headers := http.Header{} - headers.Add("Content-Type", "Application/json;charset=utf-8") + headers.Add("Content-Type", "application/json;charset=utf-8") reqBidder := &adapters.RequestData{ Method: "POST", Uri: adapter.endpoint + addParams(sellerId), //adapter.endpoint, diff --git a/config/config.go b/config/config.go index 5d538f38523..2e7f875b023 100755 --- a/config/config.go +++ b/config/config.go @@ -793,7 +793,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") - v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2") + v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") From ea348e3fd3fa4fcc04149e761676051539d92aa1 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 15 Jul 2020 23:01:29 -0400 Subject: [PATCH 141/603] Set OpenRTB DNT From HTTP Header (#1397) --- endpoints/openrtb2/auction.go | 26 +++ endpoints/openrtb2/auction_test.go | 183 ++++++++++++++++++ .../supplementary/site-has-dnt.json | 45 +++++ 3 files changed, 254 insertions(+) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 3fd2132143e..86186fa8373 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -39,6 +39,12 @@ import ( const storedRequestTimeoutMillis = 50 +var ( + dntKey string = http.CanonicalHeaderKey("DNT") + dntDisabled int8 = 0 + dntEnabled int8 = 1 +) + func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) { if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { @@ -964,6 +970,8 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) { setIPImplicitly(httpReq, bidReq, ipValidtor) setUAImplicitly(httpReq, bidReq) + setDoNotTrackImplicitly(httpReq, bidReq) + } // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, @@ -1192,6 +1200,24 @@ func setUAImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { } } +func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { + if bidReq.Device == nil || bidReq.Device.DNT == nil { + dnt := httpReq.Header.Get(dntKey) + if dnt == "0" || dnt == "1" { + if bidReq.Device == nil { + bidReq.Device = &openrtb.Device{} + } + + switch dnt { + case "0": + bidReq.Device.DNT = &dntDisabled + case "1": + bidReq.Device.DNT = &dntEnabled + } + } + } +} + // parseUserID gets this user's ID for the host machine, if it exists. func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) { if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil { diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index c697c206483..957760c61c9 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -604,6 +604,189 @@ func TestImplicitIPsEndToEnd(t *testing.T) { } } +func TestImplicitDNT(t *testing.T) { + var ( + disabled int8 = 0 + enabled int8 = 1 + ) + testCases := []struct { + description string + dntHeader string + request openrtb.BidRequest + expectedRequest openrtb.BidRequest + }{ + { + description: "Device Missing - Not Set In Header", + dntHeader: "", + request: openrtb.BidRequest{}, + expectedRequest: openrtb.BidRequest{}, + }, + { + description: "Device Missing - Set To 0 In Header", + dntHeader: "0", + request: openrtb.BidRequest{}, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &disabled, + }, + }, + }, + { + description: "Device Missing - Set To 1 In Header", + dntHeader: "1", + request: openrtb.BidRequest{}, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + }, + { + description: "Not Set In Request - Not Set In Header", + dntHeader: "", + request: openrtb.BidRequest{ + Device: &openrtb.Device{}, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{}, + }, + }, + { + description: "Not Set In Request - Set To 0 In Header", + dntHeader: "0", + request: openrtb.BidRequest{ + Device: &openrtb.Device{}, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &disabled, + }, + }, + }, + { + description: "Not Set In Request - Set To 1 In Header", + dntHeader: "1", + request: openrtb.BidRequest{ + Device: &openrtb.Device{}, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + }, + { + description: "Set In Request - Not Set In Header", + dntHeader: "", + request: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + }, + { + description: "Set In Request - Set To 0 In Header", + dntHeader: "0", + request: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + }, + { + description: "Set In Request - Set To 1 In Header", + dntHeader: "1", + request: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + expectedRequest: openrtb.BidRequest{ + Device: &openrtb.Device{ + DNT: &enabled, + }, + }, + }, + } + + for _, test := range testCases { + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil) + httpReq.Header.Set("DNT", test.dntHeader) + setDoNotTrackImplicitly(httpReq, &test.request) + assert.Equal(t, test.expectedRequest, test.request) + } +} + +func TestImplicitDNTEndToEnd(t *testing.T) { + var ( + disabled int8 = 0 + enabled int8 = 1 + ) + testCases := []struct { + description string + reqJSONFile string + dntHeader string + expectedDNT *int8 + }{ + { + description: "Not Set In Request - Not Set In Header", + reqJSONFile: "site.json", + dntHeader: "", + expectedDNT: nil, + }, + { + description: "Not Set In Request - Set To 0 In Header", + reqJSONFile: "site.json", + dntHeader: "0", + expectedDNT: &disabled, + }, + { + description: "Not Set In Request - Set To 1 In Header", + reqJSONFile: "site.json", + dntHeader: "1", + expectedDNT: &enabled, + }, + { + description: "Set In Request - Not Set In Header", + reqJSONFile: "site-has-dnt.json", + dntHeader: "", + expectedDNT: &enabled, // Hardcoded value in test file. + }, + { + description: "Set In Request - Not Overwritten By Header", + reqJSONFile: "site-has-dnt.json", + dntHeader: "0", + expectedDNT: &enabled, // Hardcoded value in test file. + }, + } + + metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + for _, test := range testCases { + exchange := &nobidExchange{} + endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) + httpReq.Header.Set("DNT", test.dntHeader) + + endpoint(httptest.NewRecorder(), httpReq, nil) + + result := exchange.gotRequest + if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") { + t.FailNow() + } + assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt") + } +} func TestImplicitSecure(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https") diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json new file mode 100644 index 00000000000..b1fae20afe4 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json @@ -0,0 +1,45 @@ +{ + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "device": { + "dnt": 1 + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + } + } + } + } + \ No newline at end of file From 55f4c453668d0b7233331067be7232b0e89f0626 Mon Sep 17 00:00:00 2001 From: Gena Date: Thu, 16 Jul 2020 17:22:23 +0300 Subject: [PATCH 142/603] Add video for InApp support (#1399) --- static/bidder-info/adtelligent.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index fe791343daf..7a20d52b266 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -4,6 +4,7 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner From 62a72e23621b20ec3e5241ba955d36c775dfb394 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 16 Jul 2020 16:36:32 -0400 Subject: [PATCH 143/603] Timeout fix (#1390) --- config/util/loggers.go | 6 +-- exchange/bidder.go | 22 +++++++--- exchange/bidder_test.go | 91 +++++++++++++++++++++++++++++---------- exchange/exchange_test.go | 9 ++++ 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/config/util/loggers.go b/config/util/loggers.go index 88702e68763..d9aad43a7fb 100644 --- a/config/util/loggers.go +++ b/config/util/loggers.go @@ -4,18 +4,18 @@ import ( "math/rand" ) -type logMsg func(string, ...interface{}) +type LogMsg func(string, ...interface{}) type randomGenerator func() float32 // LogRandomSample will log a randam sample of the messages it is sent, based on the chance to log // chance = 1.0 => always log, // chance = 0.0 => never log -func LogRandomSample(msg string, logger logMsg, chance float32) { +func LogRandomSample(msg string, logger LogMsg, chance float32) { logRandomSampleImpl(msg, logger, chance, rand.Float32) } -func logRandomSampleImpl(msg string, logger logMsg, chance float32, randGenerator randomGenerator) { +func logRandomSampleImpl(msg string, logger LogMsg, chance float32, randGenerator randomGenerator) { if chance < 1.0 && randGenerator() > chance { // this is the chance we don't log anything return diff --git a/exchange/bidder.go b/exchange/bidder.go index df9f0a3bf1b..ee6a4942147 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -312,6 +312,10 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { // doRequest makes a request, handles the response, and returns the data needed by the // Bidder interface. func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData) *httpCallInfo { + return bidder.doRequestImpl(ctx, req, glog.Warningf) +} + +func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo { httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body)) if err != nil { return &httpCallInfo{ @@ -325,12 +329,18 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques if err != nil { if err == context.DeadlineExceeded { err = &errortypes.Timeout{Message: err.Error()} - if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); ok { + var corebidder adapters.Bidder = bidder.Bidder + // The bidder adapter normally stores an info-aware bidder (a bidder wrapper) + // rather than the actual bidder. So we need to unpack that first. + if b, ok := corebidder.(*adapters.InfoAwareBidder); ok { + corebidder = b.Bidder + } + if tb, ok := corebidder.(adapters.TimeoutBidder); ok { // Toss the timeout notification call into a go routine, as we are out of time' // and cannot delay processing. We don't do anything result, as there is not much // we can do about a timeout notification failure. We do not want to get stuck in // a loop of trying to report timeouts to the timeout notifications. - go bidder.doTimeoutNotification(tb, req) + go bidder.doTimeoutNotification(tb, req, logger) } } @@ -366,7 +376,7 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques } } -func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData) { +func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() toReq, errL := timeoutBidder.MakeTimeoutNotification(req) @@ -385,13 +395,13 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou msg = fmt.Sprintf("TimeoutNotification: error:(%s) body:%s", err.Error(), string(toReq.Body)) } // If logging is turned on, and logging is not disallowed via FailOnly - util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) } } else { bidder.me.RecordTimeoutNotice(false) if bidder.DebugConfig.TimeoutNotification.Log { msg := fmt.Sprintf("TimeoutNotification: Failed to make timeout request: method(%s), uri(%s), error(%s)", toReq.Method, toReq.Uri, err.Error()) - util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) } } } else if bidder.DebugConfig.TimeoutNotification.Log { @@ -402,7 +412,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou } else { msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request marshal failed(%s)", errL[0].Error(), err.Error()) } - util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b776715adaf..d4fc0cf7cd3 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1,6 +1,7 @@ package exchange import ( + "bytes" "context" "encoding/json" "errors" @@ -10,6 +11,7 @@ import ( "testing" "time" + "github.com/golang/glog" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -1237,8 +1239,8 @@ func TestTimeoutNotificationOff(t *testing.T) { server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) defer server.Close() - bidderImpl := ¬ifingBidder{ - notiRequest: adapters.RequestData{ + bidderImpl := ¬ifyingBidder{ + notifyRequest: adapters.RequestData{ Method: "GET", Uri: server.URL + "/notify/me", Body: nil, @@ -1254,39 +1256,83 @@ func TestTimeoutNotificationOff(t *testing.T) { if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { t.Error("Failed to cast bidder to a TimeoutBidder") } else { - bidder.doTimeoutNotification(tb, &adapters.RequestData{}) + bidder.doTimeoutNotification(tb, &adapters.RequestData{}, glog.Warningf) } } func TestTimeoutNotificationOn(t *testing.T) { - respBody := "{\"bid\":false}" - respStatus := 200 - server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + // Expire context immediately to force timeout handler. + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now()) + cancelFunc() + + // Notification logic is hardcoded for 200ms. We need to wait for a little longer than that. + server := httptest.NewServer(mockSlowHandler(205*time.Millisecond, 200, `{"bid":false}`)) defer server.Close() - bidderImpl := ¬ifingBidder{ - notiRequest: adapters.RequestData{ + bidder := ¬ifyingBidder{ + notifyRequest: adapters.RequestData{ Method: "GET", Uri: server.URL + "/notify/me", Body: nil, Headers: http.Header{}, }, } - bidder := &bidderAdapter{ - Bidder: bidderImpl, + + // Wrap with BidderInfo to mimic exchange.go flow. + bidderWrappedWithInfo := wrapWithBidderInfo(bidder) + + bidderAdapter := &bidderAdapter{ + Bidder: bidderWrappedWithInfo, Client: server.Client(), DebugConfig: config.Debug{ TimeoutNotification: config.TimeoutNotification{ - Log: true, + Log: true, + SamplingRate: 1.0, }, }, me: &metricsConfig.DummyMetricsEngine{}, } - if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { - t.Error("Failed to cast bidder to a TimeoutBidder") - } else { - bidder.doTimeoutNotification(tb, &adapters.RequestData{}) + + // Unwrap To Mimic exchange.go Casting Code + var coreBidder adapters.Bidder = bidderAdapter.Bidder + if b, ok := coreBidder.(*adapters.InfoAwareBidder); ok { + coreBidder = b.Bidder + } + if _, ok := coreBidder.(adapters.TimeoutBidder); !ok { + t.Fatal("Failed to cast bidder to a TimeoutBidder") + } + + bidRequest := adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`), + } + + var loggerBuffer bytes.Buffer + logger := func(msg string, args ...interface{}) { + loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...)) + } + + bidderAdapter.doRequestImpl(ctx, &bidRequest, logger) + + // Wait a little longer than the 205ms mock server sleep. + time.Sleep(210 * time.Millisecond) + + logExpected := "TimeoutNotification: error:(context deadline exceeded) body:\n" + logActual := loggerBuffer.String() + assert.EqualValues(t, logExpected, logActual) +} + +func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { + bidderInfo := adapters.BidderInfo{ + Status: adapters.StatusActive, + Capabilities: &adapters.CapabilitiesInfo{ + App: &adapters.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, } + return adapters.EnforceBidderInfo(bidder, bidderInfo) } type goodSingleBidder struct { @@ -1363,18 +1409,19 @@ func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externa return nil, []error{errors.New("Can't make a response.")} } -type notifingBidder struct { - notiRequest adapters.RequestData +type notifyingBidder struct { + requests []*adapters.RequestData + notifyRequest adapters.RequestData } -func (bidder *notifingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - return nil, nil +func (bidder *notifyingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + return bidder.requests, nil } -func (bidder *notifingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, nil } -func (bidder *notifingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { - return &bidder.notiRequest, nil +func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { + return &bidder.notifyRequest, nil } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 161b24fd1c1..96f740de23a 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1830,6 +1830,15 @@ func mockHandler(statusCode int, getBody string, postBody string) http.Handler { }) } +func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(delay) + + w.WriteHeader(statusCode) + w.Write([]byte(body)) + }) +} + type wellBehavedCache struct{} func (c *wellBehavedCache) GetExtCacheData() (string, string) { From 5a7a2cf17b12f6ad78889c31cf1bdf7d7c71c904 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 17 Jul 2020 09:59:59 -0400 Subject: [PATCH 144/603] Privacy Request Metrics (#1400) * Privacy Request Metrics * Fix Bug + Add Unit Tests * Fixed Tests * Fix Typo --- exchange/exchange.go | 7 +- exchange/utils.go | 22 ++- exchange/utils_test.go | 198 ++++++++++++++++++++--- gdpr/impl_test.go | 2 +- pbsmetrics/config/metrics.go | 10 +- pbsmetrics/go_metrics.go | 49 ++++-- pbsmetrics/go_metrics_test.go | 65 +++++++- pbsmetrics/metrics.go | 14 +- pbsmetrics/metrics_mock.go | 6 +- pbsmetrics/prometheus/preload.go | 22 ++- pbsmetrics/prometheus/prometheus.go | 56 ++++++- pbsmetrics/prometheus/prometheus_test.go | 85 ++++++++-- 12 files changed, 454 insertions(+), 82 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 174a0b3e0fc..3f0258dd3c1 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -104,11 +104,10 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + + e.me.RecordRequestPrivacy(privacyLabels) - if cleanMetrics.gdprEnforced { - e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion)) - } // List of bidders we have requests for. liveAdapters := listBiddersWithRequests(cleanRequests) diff --git a/exchange/utils.go b/exchange/utils.go index 4de985eca40..bc1b555e507 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -19,15 +19,6 @@ import ( "github.com/prebid/prebid-server/privacy/lmt" ) -// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just -// the TCF version, but made a struct to facilitate future expansion -type cleanMetrics struct { - // A simple flag if GDPR is being enforced on this request. - gdprEnforced bool - // a zero value means a missing or invalid GDPR string - gdprTcfVersion int -} - func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) @@ -63,7 +54,7 @@ func cleanOpenRTBRequests(ctx context.Context, labels pbsmetrics.Labels, gDPR gdpr.Permissions, usersyncIfAmbiguous bool, - privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) { + privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) { impsByBidder, errs := splitImps(orig.Imp) if len(errs) > 0 { @@ -98,13 +89,20 @@ func cleanOpenRTBRequests(ctx context.Context, LMT: lmtPolicy.ShouldEnforce(), } + privacyLabels.CCPAProvided = ccpaPolicy.Value != "" + privacyLabels.CCPAEnforced = privacyEnforcement.CCPA + privacyLabels.COPPAEnforced = privacyEnforcement.COPPA + privacyLabels.LMTEnforced = privacyEnforcement.LMT + if gdpr == 1 { - cleanMetrics.gdprEnforced = true + privacyLabels.GDPREnforced = true parsedConsent, err := vendorconsent.ParseString(consent) if err == nil { - cleanMetrics.gdprTcfVersion = int(parsedConsent.Version()) + version := int(parsedConsent.Version()) + privacyLabels.GDPRTCFVersion = pbsmetrics.TCFVersionToValue(version) } } + // bidder level privacy policies for bidder, bidReq := range requestsByBidder { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 6d66e816e7b..608e6a17a10 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -15,7 +15,9 @@ import ( // permissionsMock mocks the Permissions interface for tests // // It only allows appnexus for GDPR consent -type permissionsMock struct{} +type permissionsMock struct { + personalInfoAllowed bool +} func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { return true, nil @@ -26,10 +28,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ } func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - if bidder == "appnexus" { - return true, true, nil - } - return false, false, nil + return p.personalInfoAllowed, p.personalInfoAllowed, nil } func (p *permissionsMock) AMPException() bool { @@ -80,7 +79,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -92,26 +91,48 @@ func TestCleanOpenRTBRequests(t *testing.T) { func TestCleanOpenRTBRequestsCCPA(t *testing.T) { testCases := []struct { - description string - enforceCCPA bool - expectDataScrub bool + description string + ccpaConsent string + enforceCCPA bool + expectDataScrub bool + expectPrivacyLabels pbsmetrics.PrivacyLabels }{ { - description: "Feature Flag Enabled", + description: "Feature Flag Enabled - Opt Out", + ccpaConsent: "1-Y-", enforceCCPA: true, expectDataScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: true, + CCPAEnforced: true, + }, + }, + { + description: "Feature Flag Enabled - Opt In", + ccpaConsent: "1-N-", + enforceCCPA: true, + expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: true, + CCPAEnforced: false, + }, }, { description: "Feature Flag Disabled", + ccpaConsent: "1-Y-", enforceCCPA: false, expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: false, + CCPAEnforced: false, + }, }, } for _, test := range testCases { req := newBidRequest(t) req.Regs = &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`), + Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), } privacyConfig := config.Privacy{ @@ -120,11 +141,10 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { }, } - results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) - if test.expectDataScrub { assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") @@ -132,6 +152,51 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") } + assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") + } +} + +func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { + testCases := []struct { + description string + coppa int8 + expectDataScrub bool + expectPrivacyLabels pbsmetrics.PrivacyLabels + }{ + { + description: "Enabled", + coppa: 1, + expectDataScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + COPPAEnforced: true, + }, + }, + { + description: "Disabled", + coppa: 0, + expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + COPPAEnforced: false, + }, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Regs = &openrtb.Regs{COPPA: test.coppa} + + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) + result := results["appnexus"] + + assert.Nil(t, errs) + if test.expectDataScrub { + assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.Equal(t, result.User.Yob, int64(0), test.description+":User.Yob") + } else { + assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.NotEqual(t, result.User.Yob, int64(0), test.description+":User.Yob") + } + assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") } } @@ -227,34 +292,47 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { disabled int8 = 0 ) testCases := []struct { - description string - lmt *int8 - enforceLMT bool - expectDataScrub bool + description string + lmt *int8 + enforceLMT bool + expectDataScrub bool + expectPrivacyLabels pbsmetrics.PrivacyLabels }{ { description: "Feature Flag Enabled - OpenTRB Enabled", lmt: &enabled, enforceLMT: true, expectDataScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + LMTEnforced: true, + }, }, { description: "Feature Flag Disabled - OpenTRB Enabled", lmt: &enabled, enforceLMT: false, expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + LMTEnforced: false, + }, }, { description: "Feature Flag Enabled - OpenTRB Disabled", lmt: &disabled, enforceLMT: true, expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + LMTEnforced: false, + }, }, { description: "Feature Flag Disabled - OpenTRB Disabled", lmt: &disabled, enforceLMT: false, expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + LMTEnforced: false, + }, }, } @@ -268,11 +346,10 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) - if test.expectDataScrub { assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") @@ -280,6 +357,88 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") } + assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") + } +} + +func TestCleanOpenRTBRequestsGDPR(t *testing.T) { + testCases := []struct { + description string + gdpr string + gdprConsent string + gdprScrub bool + enforceGDPR bool + expectPrivacyLabels pbsmetrics.PrivacyLabels + }{ + { + description: "Enforce - TCF Invalid", + gdpr: "1", + gdprConsent: "malformed", + gdprScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: "", + }, + }, + { + description: "Enforce - TCF 1", + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV1, + }, + }, + { + description: "Enforce - TCF 2", + gdpr: "1", + gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + gdprScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV2, + }, + }, + { + description: "Not Enforce - TCF 1", + gdpr: "0", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: false, + GDPRTCFVersion: "", + }, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) + req.Regs = &openrtb.Regs{ + Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`), + } + + privacyConfig := config.Privacy{ + GDPR: config.GDPR{ + TCF2: config.TCF2{ + Enabled: true, + }, + }, + } + + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig) + result := results["appnexus"] + + assert.Nil(t, errs) + if test.gdprScrub { + assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") + } else { + assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID") + assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5") + } + assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") } } @@ -352,6 +511,7 @@ func newBidRequest(t *testing.T) *openrtb.BidRequest { User: &openrtb.User{ ID: "our-id", BuyerUID: "their-id", + Yob: 1982, Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, Imp: []openrtb.Imp{{ diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f05f25e87ea..05b2fb6d98e 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -276,7 +276,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 // PI needs all purposes to succeed testDefs := []tcf2TestDef{ { diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index 3d105dead44..0dbe9a69d9f 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -195,10 +195,10 @@ func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) { } } -// RecordTCFReq across all engines -func (me *MultiMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) { +// RecordRequestPrivacy across all engines +func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) { for _, thisME := range *me { - thisME.RecordTCFReq(version) + thisME.RecordRequestPrivacy(privacy) } } @@ -281,6 +281,6 @@ func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType p func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { } -// RecordReq as a noop -func (me *DummyMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) { +// RecordRequestPrivacy as a noop +func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) { } diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index 73eb30a1504..836434bf25e 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -53,7 +53,11 @@ type Metrics struct { TimeoutNotificationFailure metrics.Meter // TCF adaption metrics - TCFReqVersion map[TCFVersionValue]metrics.Meter + PrivacyCCPARequest metrics.Meter + PrivacyCCPARequestOptOut metrics.Meter + PrivacyCOPPARequest metrics.Meter + PrivacyLMTRequest metrics.Meter + PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically @@ -141,7 +145,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa TimeoutNotificationSuccess: blankMeter, TimeoutNotificationFailure: blankMeter, - TCFReqVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())), + PrivacyCCPARequest: blankMeter, + PrivacyCCPARequestOptOut: blankMeter, + PrivacyCOPPARequest: blankMeter, + PrivacyLMTRequest: blankMeter, + PrivacyTCFRequestVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())), AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), @@ -149,6 +157,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa exchanges: exchanges, } + for _, a := range exchanges { newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics() } @@ -166,7 +175,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa } for _, v := range TCFVersions() { - newMetrics.TCFReqVersion[v] = blankMeter + newMetrics.PrivacyTCFRequestVersion[v] = blankMeter } //to minimize memory usage, queuedTimeout metric is now supported for video endpoint only @@ -234,8 +243,12 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry) newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry) + newMetrics.PrivacyCCPARequest = metrics.GetOrRegisterMeter("privacy.request.ccpa.specified", registry) + newMetrics.PrivacyCCPARequestOptOut = metrics.GetOrRegisterMeter("privacy.request.ccpa.opt-out", registry) + newMetrics.PrivacyCOPPARequest = metrics.GetOrRegisterMeter("privacy.request.coppa", registry) + newMetrics.PrivacyLMTRequest = metrics.GetOrRegisterMeter("privacy.request.lmt", registry) for _, version := range TCFVersions() { - newMetrics.TCFReqVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry) + newMetrics.PrivacyTCFRequestVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry) } return newMetrics @@ -582,12 +595,28 @@ func (me *Metrics) RecordTimeoutNotice(success bool) { return } -func (me *Metrics) RecordTCFReq(version TCFVersionValue) { - met, ok := me.TCFReqVersion[version] - if ok { - met.Mark(1) - } else { - me.TCFReqVersion[TCFVersionErr].Mark(1) +func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { + if privacy.CCPAProvided { + me.PrivacyCCPARequest.Mark(1) + if privacy.CCPAEnforced { + me.PrivacyCCPARequestOptOut.Mark(1) + } + } + + if privacy.COPPAEnforced { + me.PrivacyCOPPARequest.Mark(1) + } + + if privacy.GDPREnforced { + if metric, ok := me.PrivacyTCFRequestVersion[privacy.GDPRTCFVersion]; ok { + metric.Mark(1) + } else { + me.PrivacyTCFRequestVersion[TCFVersionErr].Mark(1) + } + } + + if privacy.LMTEnforced { + me.PrivacyLMTRequest.Mark(1) } return } diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index 6d9eaf9f0e9..2faa08491e0 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -56,10 +56,14 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess) ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure) - ensureContains(t, registry, "privacy.request.tcf.v1", m.TCFReqVersion[TCFVersionV1]) - ensureContains(t, registry, "privacy.request.tcf.v2", m.TCFReqVersion[TCFVersionV2]) - ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqVersion[TCFVersionErr]) + ensureContains(t, registry, "privacy.request.ccpa.specified", m.PrivacyCCPARequest) + ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut) + ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest) + ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest) + ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1]) + ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2]) + ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr]) } func TestRecordBidType(t *testing.T) { @@ -202,6 +206,61 @@ func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) { assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(1)) } +func TestRecordRequestPrivacy(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) + + // CCPA + m.RecordRequestPrivacy(PrivacyLabels{ + CCPAEnforced: true, + CCPAProvided: true, + }) + m.RecordRequestPrivacy(PrivacyLabels{ + CCPAEnforced: true, + CCPAProvided: false, + }) + m.RecordRequestPrivacy(PrivacyLabels{ + CCPAEnforced: false, + CCPAProvided: true, + }) + + // COPPA + m.RecordRequestPrivacy(PrivacyLabels{ + COPPAEnforced: true, + }) + + // LMT + m.RecordRequestPrivacy(PrivacyLabels{ + LMTEnforced: true, + }) + + // GDPR + m.RecordRequestPrivacy(PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: TCFVersionErr, + }) + m.RecordRequestPrivacy(PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: TCFVersionV1, + }) + m.RecordRequestPrivacy(PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: TCFVersionV2, + }) + m.RecordRequestPrivacy(PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: TCFVersionV1, + }) + + assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA") + assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out") + assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA") + assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT") + assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err") + assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1") + assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") +} + func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) { ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter) ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter) diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index 0e94fe71e90..514fbac1015 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -41,6 +41,16 @@ type RequestLabels struct { RequestStatus RequestStatus } +// PrivacyLabels defines metrics describing the result of privacy enforcement. +type PrivacyLabels struct { + CCPAEnforced bool + CCPAProvided bool + COPPAEnforced bool + GDPREnforced bool + GDPRTCFVersion TCFVersionValue + LMTEnforced bool +} + // Label typecasting. Se below the type definitions for possible values // DemandSource : Demand source enumeration @@ -257,7 +267,7 @@ const ( TCFVersionV2 TCFVersionValue = "v2" ) -// TCFVersions rtuens the possible values for the TCF version +// TCFVersions returns the possible values for the TCF version func TCFVersions() []TCFVersionValue { return []TCFVersionValue{ TCFVersionErr, @@ -305,5 +315,5 @@ type MetricsEngine interface { RecordPrebidCacheRequestTime(success bool, length time.Duration) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) RecordTimeoutNotice(sucess bool) - RecordTCFReq(version TCFVersionValue) + RecordRequestPrivacy(privacy PrivacyLabels) } diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index a6d36a72401..6c263f0af4d 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -107,7 +107,7 @@ func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) { me.Called(success) } -// RecordTCFReq mock -func (me *MetricsEngineMock) RecordTCFReq(version TCFVersionValue) { - me.Called(version) +// RecordRequestPrivacy mock +func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) { + me.Called(privacy) } diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go index 19f4f225af9..ef1d300c4df 100644 --- a/pbsmetrics/prometheus/preload.go +++ b/pbsmetrics/prometheus/preload.go @@ -8,15 +8,16 @@ import ( func preloadLabelValues(m *Metrics) { var ( actionValues = actionsAsString() - adapterValues = adaptersAsString() adapterErrorValues = adapterErrorsAsString() + adapterValues = adaptersAsString() bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} boolValues = boolValuesAsString() cacheResultValues = cacheResultsAsString() - cookieValues = cookieTypesAsString() connectionErrorValues = []string{connectionAcceptError, connectionCloseError} + cookieValues = cookieTypesAsString() requestStatusValues = requestStatusesAsString() requestTypeValues = requestTypesAsString() + sourceValues = []string{sourceRequest} ) preloadLabelValuesForCounter(m.connectionsError, map[string][]string{ @@ -100,9 +101,22 @@ func preloadLabelValues(m *Metrics) { requestStatusLabel: {requestSuccessLabel, requestRejectLabel}, }) - preloadLabelValuesForCounter(m.tcfVersion, map[string][]string{ + preloadLabelValuesForCounter(m.privacyCCPA, map[string][]string{ + sourceLabel: sourceValues, + optOutLabel: boolValues, + }) + + preloadLabelValuesForCounter(m.privacyCOPPA, map[string][]string{ + sourceLabel: sourceValues, + }) + + preloadLabelValuesForCounter(m.privacyLMT, map[string][]string{ + sourceLabel: sourceValues, + }) + + preloadLabelValuesForCounter(m.privacyTCF, map[string][]string{ + sourceLabel: sourceValues, versionLabel: tcfVersionsAsString(), - sourceLabel: {string(sourceRequest)}, }) } diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index bf854746fd2..d94c4d78f62 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -29,7 +29,10 @@ type Metrics struct { storedImpressionsCacheResult *prometheus.CounterVec storedRequestCacheResult *prometheus.CounterVec timeoutNotifications *prometheus.CounterVec - tcfVersion *prometheus.CounterVec + privacyCCPA *prometheus.CounterVec + privacyCOPPA *prometheus.CounterVec + privacyLMT *prometheus.CounterVec + privacyTCF *prometheus.CounterVec // Adapter Metrics adapterBids *prometheus.CounterVec @@ -60,6 +63,7 @@ const ( isNativeLabel = "native" isVideoLabel = "video" markupDeliveryLabel = "delivery" + optOutLabel = "opt_out" privacyBlockedLabel = "privacy_blocked" requestStatusLabel = "request_status" requestTypeLabel = "request_type" @@ -165,11 +169,26 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of timeout notifications triggered, and if they were successfully sent.", []string{successLabel}) - metrics.tcfVersion = newCounter(cfg, metrics.Registry, + metrics.privacyCCPA = newCounter(cfg, metrics.Registry, + "privacy_ccpa", + "Count of total requests to Prebid Server where CCPA was provided by source and opt-out .", + []string{sourceLabel, optOutLabel}) + + metrics.privacyCOPPA = newCounter(cfg, metrics.Registry, + "privacy_coppa", + "Count of total requests to Prebid Server where the COPPA flag was set by source", + []string{sourceLabel}) + + metrics.privacyTCF = newCounter(cfg, metrics.Registry, "privacy_tcf", - "Count of TCF versions for requests where GDPR was enforced.", + "Count of TCF versions for requests where GDPR was enforced by source and version.", []string{versionLabel, sourceLabel}) + metrics.privacyLMT = newCounter(cfg, metrics.Registry, + "privacy_lmt", + "Count of total requests to Prebid Server where the LMT flag was set by source", + []string{sourceLabel}) + metrics.adapterBids = newCounter(cfg, metrics.Registry, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -434,9 +453,30 @@ func (m *Metrics) RecordTimeoutNotice(success bool) { } } -func (m *Metrics) RecordTCFReq(version pbsmetrics.TCFVersionValue) { - m.tcfVersion.With(prometheus.Labels{ - versionLabel: string(version), - sourceLabel: sourceRequest, - }).Inc() +func (m *Metrics) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) { + if privacy.CCPAProvided { + m.privacyCCPA.With(prometheus.Labels{ + sourceLabel: sourceRequest, + optOutLabel: strconv.FormatBool(privacy.CCPAEnforced), + }).Inc() + } + + if privacy.COPPAEnforced { + m.privacyCOPPA.With(prometheus.Labels{ + sourceLabel: sourceRequest, + }).Inc() + } + + if privacy.GDPREnforced { + m.privacyTCF.With(prometheus.Labels{ + versionLabel: string(privacy.GDPRTCFVersion), + sourceLabel: sourceRequest, + }).Inc() + } + + if privacy.LMTEnforced { + m.privacyLMT.With(prometheus.Labels{ + sourceLabel: sourceRequest, + }).Inc() + } } diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index 03daff0d56b..b722ab28b5c 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -944,33 +944,96 @@ func TestTimeoutNotifications(t *testing.T) { } -func TestTCFMetrics(t *testing.T) { +func TestRecordRequestPrivacy(t *testing.T) { m := createMetricsForTesting() - m.RecordTCFReq(pbsmetrics.TCFVersionToValue(0)) - m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1)) - m.RecordTCFReq(pbsmetrics.TCFVersionToValue(2)) - m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1)) + // CCPA + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + CCPAEnforced: true, + CCPAProvided: true, + }) + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + CCPAEnforced: true, + CCPAProvided: false, + }) + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + CCPAEnforced: false, + CCPAProvided: true, + }) + + // COPPA + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + COPPAEnforced: true, + }) - assertCounterVecValue(t, "", "privacy_tcf:err", m.tcfVersion, + // LMT + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + LMTEnforced: true, + }) + + // GDPR + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionErr, + }) + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV1, + }) + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV2, + }) + m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV1, + }) + + assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, + float64(1), + prometheus.Labels{ + sourceLabel: sourceRequest, + optOutLabel: "true", + }) + + assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, + float64(1), + prometheus.Labels{ + sourceLabel: sourceRequest, + optOutLabel: "false", + }) + + assertCounterVecValue(t, "", "privacy_coppa", m.privacyCOPPA, + float64(1), + prometheus.Labels{ + sourceLabel: sourceRequest, + }) + + assertCounterVecValue(t, "", "privacy_lmt", m.privacyLMT, + float64(1), + prometheus.Labels{ + sourceLabel: sourceRequest, + }) + + assertCounterVecValue(t, "", "privacy_tcf:err", m.privacyTCF, float64(1), prometheus.Labels{ - versionLabel: "err", sourceLabel: sourceRequest, + versionLabel: "err", }) - assertCounterVecValue(t, "", "privacy_tcf:v1", m.tcfVersion, + assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF, float64(2), prometheus.Labels{ - versionLabel: "v1", sourceLabel: sourceRequest, + versionLabel: "v1", }) - assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfVersion, + assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF, float64(1), prometheus.Labels{ - versionLabel: "v2", sourceLabel: sourceRequest, + versionLabel: "v2", }) } From 0ccb77388da8d96fe6e1ee512d17fa415e607c70 Mon Sep 17 00:00:00 2001 From: Daniel Barrigas Date: Fri, 17 Jul 2020 16:32:22 +0100 Subject: [PATCH 145/603] Parse Site.Publisher.ID from Amp Auction HTTP Req Query Parameter "account" (#1403) --- endpoints/openrtb2/amp_auction.go | 8 ++++++++ endpoints/openrtb2/amp_auction_test.go | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index e8b5d3ecc76..8efba5a926c 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -388,6 +388,14 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope setAmpExt(req.Site, "1") + account := httpRequest.FormValue("account") + if account != "" { + if req.Site.Publisher == nil { + req.Site.Publisher = &openrtb.Publisher{} + } + req.Site.Publisher.ID = account + } + slot := httpRequest.FormValue("slot") if slot != "" { req.Imp[0].TagID = slot diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 731fd55e196..692d3fb0c5d 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -755,8 +755,9 @@ func TestQueryParamOverrides(t *testing.T) { curl := "http://example.com" slot := "1234" timeout := int64(500) + account := "12345" - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d", requestID, curl, slot, timeout), nil) + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d&account=%s", requestID, curl, slot, timeout, account), nil) recorder := httptest.NewRecorder() endpoint(recorder, request, nil) @@ -784,6 +785,10 @@ func TestQueryParamOverrides(t *testing.T) { if resolvedRequest.Site == nil || resolvedRequest.Site.Page != curl { t.Errorf("Expected Site.Page to equal curl (%s), got: %s", curl, resolvedRequest.Site.Page) } + + if resolvedRequest.Site == nil || resolvedRequest.Site.Publisher == nil || resolvedRequest.Site.Publisher.ID != account { + t.Errorf("Expected Site.Publisher.ID to equal (%s), got: %s", account, resolvedRequest.Site.Publisher.ID) + } } func TestOverrideDimensions(t *testing.T) { @@ -876,6 +881,7 @@ type formatOverrideSpec struct { overrideWidth uint64 overrideHeight uint64 multisize string + account string expect []openrtb.Format } @@ -897,7 +903,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { openrtb_ext.BidderMap, ) - url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize) + url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account) request := httptest.NewRequest("GET", url, nil) recorder := httptest.NewRecorder() endpoint(recorder, request, nil) From bfcfefe2d225ad7b09a80567f3a19e4be5f4305a Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 17 Jul 2020 13:05:19 -0400 Subject: [PATCH 146/603] Facebook Only Supports App Impressions (#1396) --- .../exemplary/banner-site.json | 132 ------------------ .../exemplary/interstitial.json | 12 +- .../exemplary/native-1.1.json | 12 +- .../audienceNetworktest/exemplary/video.json | 12 +- .../supplemental/banner-format-only.json | 12 +- .../supplemental/invalid-adm.json | 12 +- .../supplemental/invalid-banner-height.json | 6 +- .../supplemental/invalid-interstitial.json | 6 +- .../supplemental/missing-adm-bidid.json | 12 +- .../supplemental/missing-adm.json | 12 +- .../supplemental/missing-banner-height.json | 6 +- .../supplemental/multi-imp.json | 18 +-- .../supplemental/no-bid-204.json | 12 +- .../supplemental/no-imps.json | 6 +- .../supplemental/required-buyeruid.json | 6 +- .../required-param-placementId.json | 6 +- .../required-param-publisherId.json | 6 +- .../supplemental/server-error-500.json | 12 +- .../supplemental/site-not-supported.json | 38 +++++ .../supplemental/split-placementId.json | 12 +- adapters/audienceNetwork/facebook.go | 20 ++- adapters/audienceNetwork/facebook_test.go | 17 --- static/bidder-info/audienceNetwork.yaml | 5 - 23 files changed, 137 insertions(+), 255 deletions(-) delete mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json deleted file mode 100644 index 01bab3dfd71..00000000000 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-req-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "publisherid": "123", - "placementid": "456" - } - } - } - ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" - }, - "device": { - "ip": "152.193.6.74" - }, - "user": { - "id": "db089de9-a62e-4861-a881-0ff15e052516", - "buyeruid": "v4_bidder_token" - }, - "tmax": 500 - }, - "httpcalls": [ - { - "expectedRequest": { - "uri": "https://an.facebook.com/placementbid.ortb", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json;charset=utf-8" - ], - "X-Fb-Pool-Routing-Token": [ - "v4_bidder_token" - ] - }, - "body": { - "id": "test-imp-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": -1, - "h": 250 - }, - "tagid": "123_456" - } - ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", - "publisher": { - "id": "123" - } - }, - "device": { - "ip": "152.193.6.74" - }, - "user": { - "id": "db089de9-a62e-4861-a881-0ff15e052516", - "buyeruid": "v4_bidder_token" - }, - "tmax": 500, - "ext": { - "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", - "platformid": "test-platform-id" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-imp-id", - "seatbid": [ - { - "bid": [ - { - "id": "987", - "impid": "test-imp-id", - "price": 1.000000, - "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", - "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", - "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", - "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" - } - ] - } - ], - "bidid": "654", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "987", - "impid": "test-imp-id", - "price": 1, - "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", - "adid": "987", - "crid": "987", - "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", - "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", - "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json index 9f563f11948..573032c81e1 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json @@ -23,9 +23,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -64,9 +64,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json index 16bed344767..08639bee013 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json @@ -16,9 +16,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -56,9 +56,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json index 5ece0f08530..35bdf9a443e 100644 --- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json @@ -21,9 +21,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -66,9 +66,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json index 5469fefbd65..450e0d9e45b 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json @@ -24,9 +24,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -64,9 +64,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json index f145f5fe4ce..c33807bda74 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json @@ -18,9 +18,9 @@ } } }], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -55,9 +55,9 @@ }, "tagid": "123_456" }], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json index fa9fd9132b8..b229d41a27a 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json @@ -22,9 +22,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json index ad19d94c6e9..68ca8044812 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json @@ -20,9 +20,9 @@ } } }], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json index b57c900104e..50212155752 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json @@ -18,9 +18,9 @@ } } }], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -55,9 +55,9 @@ }, "tagid": "123_456" }], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json index 23227aab959..832b16dca22 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json @@ -18,9 +18,9 @@ } } }], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -55,9 +55,9 @@ }, "tagid": "123_456" }], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json index 016e8de0ef0..0793f990049 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json @@ -20,9 +20,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json index 231c2826548..682c33e46b8 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json @@ -41,9 +41,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -81,9 +81,9 @@ "tagid": "pub1_plmt1" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "pub1" } @@ -152,9 +152,9 @@ "tagid": "pub2_plmt2" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "pub2" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json index 45b35e05dd9..642e495810a 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json @@ -16,9 +16,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -56,9 +56,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json index 7420f7e8fb2..fccdf71ca4a 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json @@ -2,9 +2,9 @@ "mockBidRequest": { "id": "test-req-id", "imp": [], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json index 964dcb48b48..72b4fbacdd1 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json @@ -26,9 +26,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json index a9c3c23d298..f13b70e1be2 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json @@ -25,9 +25,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json index c50f3d36378..a80a1e09b65 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json @@ -25,9 +25,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json index 7ff8886139a..f0a11905cf8 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json @@ -14,9 +14,9 @@ } } }], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -51,9 +51,9 @@ }, "tagid": "123_456" }], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json new file mode 100644 index 00000000000..9155352a192 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + } + } + }], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "expectedMakeRequestsErrors": [{ + "value": "Site impressions are not supported.", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json index 34c1eccc58e..45c34192ea2 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json @@ -21,9 +21,9 @@ } } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org" + "app": { + "id": "app-abc", + "bundle": "com.prebid" }, "device": { "ip": "152.193.6.74" @@ -50,9 +50,9 @@ "tagid": "123_456" } ], - "site": { - "domain": "prebid.org", - "page": "prebid.org", + "app": { + "id": "app-abc", + "bundle": "com.prebid", "publisher": { "id": "123" } diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index f4091e4e23c..d9f6719fd17 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -54,6 +54,12 @@ func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * }} } + if request.Site != nil { + return nil, []error{&errortypes.BadInput{ + Message: "Site impressions are not supported.", + }} + } + return this.buildRequests(request) } @@ -143,10 +149,6 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { app := *out.App app.Publisher = &openrtb.Publisher{ID: pubId} out.App = &app - } else { - site := *out.Site - site.Publisher = &openrtb.Publisher{ID: pubId} - out.Site = &site } if err = this.modifyImp(imp); err != nil { @@ -468,15 +470,11 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (* return &adapters.RequestData{}, []error{err} } - // The publisher ID is either in the app object or the site object, depending on the supply of the request so we need - // to check both + // The publisher ID is expected in the app object pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id") if err != nil { - pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id") - if err != nil { - return &adapters.RequestData{}, []error{ - errors.New("path [app|site].publisher.id not found in the request"), - } + return &adapters.RequestData{}, []error{ + errors.New("path app.publisher.id not found in the request"), } } diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 7f567d6137b..912f12223f8 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -61,23 +61,6 @@ func TestMakeTimeoutNoticeApp(t *testing.T) { assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.") } -func TestMakeTimeoutNoticeSite(t *testing.T) { - req := adapters.RequestData{ - Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`), - } - fba := NewFacebookBidder("test-platform-id", "test-app-secret") - - tb, ok := fba.(adapters.TimeoutBidder) - if !ok { - t.Error("Facebook adapter is not a TimeoutAdapter") - } - - toReq, err := tb.MakeTimeoutNotification(&req) - assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err) - expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2" - assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.") -} - func TestMakeTimeoutNoticeBadRequest(t *testing.T) { req := adapters.RequestData{ Body: []byte(`{"imp":[{{"id":"1234"}}`), diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml index 56230bf3f9a..324e5c6dff8 100644 --- a/static/bidder-info/audienceNetwork.yaml +++ b/static/bidder-info/audienceNetwork.yaml @@ -1,11 +1,6 @@ maintainer: email: "none" capabilities: - site: - mediaTypes: - - banner - - video - - native app: mediaTypes: - banner From c889570b14dac808f95fd46d7254d124e7b0c226 Mon Sep 17 00:00:00 2001 From: Ad Generation Date: Sat, 18 Jul 2020 02:39:31 +0900 Subject: [PATCH 147/603] fix: Change currency of ad-generation's bidResponse according to bidRequest (#1383) --- adapters/adgeneration/adgeneration.go | 3 +- adapters/adgeneration/adgeneration_test.go | 63 +++++++++++++++++++ .../exemplary/single-banner.json | 2 +- .../supplemental/204-bid-response.json | 2 +- .../supplemental/400-bid-response.json | 2 +- .../supplemental/no-bid-response.json | 2 +- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index 4b1215dea9d..054fa7f6df3 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -210,6 +210,7 @@ func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex Bid: &bid, BidType: bitType, }) + bidResponse.Currency = adg.getCurrency(internalRequest) return bidResponse, nil } } @@ -254,7 +255,7 @@ func removeWrapper(ad string) string { func NewAdgenerationAdapter(endpoint string) *AdgenerationAdapter { return &AdgenerationAdapter{ endpoint, - "1.0.0", + "1.0.1", "JPY", } } diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index e76995fc5e4..3c795ea28a8 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -5,7 +5,9 @@ import ( "testing" "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -174,3 +176,64 @@ func TestCreateAd(t *testing.T) { t.Errorf("%v does not match createAd.", adgVastResponse) } } + +func TestMakeBids(t *testing.T) { + bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1") + internalRequest := &openrtb.BidRequest{ + ID: "test-success-bid-request", + Imp: []openrtb.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + }, + Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb.Site{Page: "https://supership.com"}, + User: &openrtb.User{BuyerUID: "buyerID"}, + } + externalRequest := adapters.RequestData{} + response := adapters.ResponseData{ + StatusCode: 200, + Body: ([]byte)("{\n \"ad\": \"testAd\",\n \"cpm\": 30,\n \"creativeid\": \"Dummy_supership.jp\",\n \"h\": 250,\n \"locationid\": \"58278\",\n \"results\": [{}],\n \"dealid\": \"test-deal-id\",\n \"w\": 300\n }"), + } + // default Currency InternalRequest + defaultCurBidderResponse, errs := bidder.MakeBids(internalRequest, &externalRequest, &response) + if len(errs) > 0 { + t.Errorf("MakeBids return errors. errors: %v", errs) + } + checkBidResponse(t, defaultCurBidderResponse, bidder.defaultCurrency) + + // Specified Currency InternalRequest + usdCur := "USD" + internalRequest.Cur = []string{usdCur} + specifiedCurBidderResponse, errs := bidder.MakeBids(internalRequest, &externalRequest, &response) + if len(errs) > 0 { + t.Errorf("MakeBids return errors. errors: %v", errs) + } + checkBidResponse(t, specifiedCurBidderResponse, usdCur) + +} + +func checkBidResponse(t *testing.T, bidderResponse *adapters.BidderResponse, expectedCurrency string) { + if bidderResponse == nil { + t.Errorf("actual bidResponse is nil.") + } + + // AdM is assured by TestCreateAd and JSON tests + var expectedAdM string = "testAd" + var expectedID string = "58278" + var expectedImpID = "bidRequest-success-test" + var expectedPrice float64 = 30.0 + var expectedW uint64 = 300 + var expectedH uint64 = 250 + var expectedCrID string = "Dummy_supership.jp" + var extectedDealID string = "test-deal-id" + + assert.Equal(t, expectedCurrency, bidderResponse.Currency) + assert.Equal(t, 1, len(bidderResponse.Bids)) + assert.Equal(t, expectedID, bidderResponse.Bids[0].Bid.ID) + assert.Equal(t, expectedImpID, bidderResponse.Bids[0].Bid.ImpID) + assert.Equal(t, expectedAdM, bidderResponse.Bids[0].Bid.AdM) + assert.Equal(t, expectedPrice, bidderResponse.Bids[0].Bid.Price) + assert.Equal(t, expectedW, bidderResponse.Bids[0].Bid.W) + assert.Equal(t, expectedH, bidderResponse.Bids[0].Bid.H) + assert.Equal(t, expectedCrID, bidderResponse.Bids[0].Bid.CrID) + assert.Equal(t, extectedDealID, bidderResponse.Bids[0].Bid.DealID) +} diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json index d23a510bee5..10bf1c4a0c0 100644 --- a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json +++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json @@ -52,7 +52,7 @@ "tmax": 500 }, "expectedRequest":{ - "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", + "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", "headers": { "Accept": [ "application/json" diff --git a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json index cf8635bbc3d..bc469a1e3a9 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json @@ -52,7 +52,7 @@ "tmax": 500 }, "expectedRequest":{ - "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", + "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", "headers": { "Accept": [ "application/json" diff --git a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json index f5dc7fe0af5..6ac92d9a38b 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json @@ -52,7 +52,7 @@ "tmax": 500 }, "expectedRequest":{ - "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", + "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", "headers": { "Accept": [ "application/json" diff --git a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json index 399f85a5856..a0abb66d039 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json @@ -52,7 +52,7 @@ "tmax": 500 }, "expectedRequest":{ - "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", + "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html", "headers": { "Accept": [ "application/json" From 6b7c113b76ed202c9022d063bc5b713ae53ae0a6 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Fri, 17 Jul 2020 22:50:22 -0400 Subject: [PATCH 148/603] Adding primary categories to freewheel mapping (#1407) --- .../category-mapping/freewheel/freewheel.json | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/static/category-mapping/freewheel/freewheel.json b/static/category-mapping/freewheel/freewheel.json index 1c4a4fa2471..11529206087 100644 --- a/static/category-mapping/freewheel/freewheel.json +++ b/static/category-mapping/freewheel/freewheel.json @@ -1178,5 +1178,81 @@ "IAB22-3": { "id": "410", "name": "Product" + }, + "IAB1": { + "id": "392", + "name": "Entertainment" + }, + "IAB2": { + "id": "399", + "name": "Automotive" + }, + "IAB3": { + "id": "393", + "name": "Business Services" + }, + "IAB4": { + "id": "405", + "name": "Educational Services" + }, + "IAB5": { + "id": "405", + "name": "Educational Services" + }, + "IAB7": { + "id": "406", + "name": "Health Care Services" + }, + "IAB8": { + "id": "394", + "name": "Food" + }, + "IAB9": { + "id": "392", + "name": "Entertainment" + }, + "IAB10": { + "id": "434", + "name": "Home Furnishings" + }, + "IAB11": { + "id": "398", + "name": "Government/Municipal" + }, + "IAB12": { + "id": "438", + "name": "News" + }, + "IAB13": { + "id": "393", + "name": "Business Services" + }, + "IAB16": { + "id": "423", + "name": "Pet Food/Supplies" + }, + "IAB17": { + "id": "425", + "name": "Professional Sports" + }, + "IAB18": { + "id": "397", + "name": "Apparel" + }, + "IAB19": { + "id": "409", + "name": "Computing Product" + }, + "IAB20": { + "id": "395", + "name": "Travel/Hotel/Airlines" + }, + "IAB21": { + "id": "416", + "name": "Real Estate" + }, + "IAB22": { + "id": "403", + "name": "Retail Stores/Chains" } } \ No newline at end of file From a5962de9a5900f3b205dfac1263f53d7daf96eec Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 22 Jul 2020 13:11:25 -0400 Subject: [PATCH 149/603] Add Outgoing Connection Metrics (#1343) --- config/config.go | 6 + config/config_test.go | 3 + exchange/adapter_map.go | 2 +- exchange/bidder.go | 77 ++++++++-- exchange/bidder_test.go | 130 ++++++++++++++--- exchange/targeting_test.go | 2 +- go.mod | 1 + go.sum | 5 + pbsmetrics/config/metrics.go | 25 +++- pbsmetrics/go_metrics.go | 52 ++++++- pbsmetrics/go_metrics_test.go | 139 ++++++++++++++++++ pbsmetrics/metrics.go | 2 + pbsmetrics/metrics_mock.go | 10 ++ pbsmetrics/prometheus/preload.go | 14 ++ pbsmetrics/prometheus/prometheus.go | 105 +++++++++++--- pbsmetrics/prometheus/prometheus_test.go | 174 ++++++++++++++++++++++- 16 files changed, 683 insertions(+), 64 deletions(-) diff --git a/config/config.go b/config/config.go index 2e7f875b023..a82dbb5edf7 100755 --- a/config/config.go +++ b/config/config.go @@ -379,6 +379,11 @@ type Metrics struct { type DisabledMetrics struct { // True if we want to stop collecting account-to-adapter metrics AccountAdapterDetails bool `mapstructure:"account_adapter_details"` + + // True if we don't want to collect metrics about the connections prebid + // server establishes with bidder servers such as the number of connections + // that were created or reused. + AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"` } func (cfg *Metrics) validate(errs configErrors) configErrors { @@ -688,6 +693,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) + v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) v.SetDefault("metrics.influxdb.host", "") v.SetDefault("metrics.influxdb.database", "") v.SetDefault("metrics.influxdb.username", "") diff --git a/config/config_test.go b/config/config_test.go index 3456694db5c..4774d9d6e46 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -33,6 +33,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_required", cfg.AccountRequired, false) cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false) + cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") } @@ -89,6 +90,7 @@ metrics: metric_send_interval: 30 disabled_metrics: account_adapter_details: true + adapter_connections_metrics: true datacache: type: postgres filename: /usr/db/db.db @@ -294,6 +296,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "adapters.rhythmone.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].UserSyncURL, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Fprebid-server.prebid.org%2F%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") cmpBools(t, "account_required", cfg.AccountRequired, true) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) + cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 53607ac57d8..2ecddb83cfc 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -201,7 +201,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter for name, bidder := range ortbBidders { // Clean out any disabled bidders if infos[string(name)].Status == adapters.StatusActive { - allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me) + allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me, name) } } diff --git a/exchange/bidder.go b/exchange/bidder.go index ee6a4942147..7c39b72b348 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/http/httptrace" "time" "github.com/golang/glog" @@ -87,20 +88,30 @@ type pbsOrtbSeatBid struct { // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) -func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine) adaptedBidder { +func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine, name openrtb_ext.BidderName) adaptedBidder { return &bidderAdapter{ - Bidder: bidder, - Client: client, - DebugConfig: cfg.Debug, - me: me, + Bidder: bidder, + BidderName: name, + Client: client, + me: me, + config: bidderAdapterConfig{ + Debug: cfg.Debug, + DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics, + }, } } type bidderAdapter struct { - Bidder adapters.Bidder - Client *http.Client - DebugConfig config.Debug - me pbsmetrics.MetricsEngine + Bidder adapters.Bidder + BidderName openrtb_ext.BidderName + Client *http.Client + me pbsmetrics.MetricsEngine + config bidderAdapterConfig +} + +type bidderAdapterConfig struct { + Debug config.Debug + DisableConnMetrics bool } func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currencies.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { @@ -325,6 +336,11 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re } httpReq.Header = req.Headers + // If adapter connection metrics are not disabled, add the client trace + // to get complete connection info into our metrics + if !bidder.config.DisableConnMetrics { + ctx = bidder.addClientTrace(ctx) + } httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq) if err != nil { if err == context.DeadlineExceeded { @@ -387,7 +403,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq) success := (err == nil && httpResp.StatusCode >= 200 && httpResp.StatusCode < 300) bidder.me.RecordTimeoutNotice(success) - if bidder.DebugConfig.TimeoutNotification.Log && !(bidder.DebugConfig.TimeoutNotification.FailOnly && success) { + if bidder.config.Debug.TimeoutNotification.Log && !(bidder.config.Debug.TimeoutNotification.FailOnly && success) { var msg string if err == nil { msg = fmt.Sprintf("TimeoutNotification: status:(%d) body:%s", httpResp.StatusCode, string(toReq.Body)) @@ -395,16 +411,16 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou msg = fmt.Sprintf("TimeoutNotification: error:(%s) body:%s", err.Error(), string(toReq.Body)) } // If logging is turned on, and logging is not disallowed via FailOnly - util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate) } } else { bidder.me.RecordTimeoutNotice(false) - if bidder.DebugConfig.TimeoutNotification.Log { + if bidder.config.Debug.TimeoutNotification.Log { msg := fmt.Sprintf("TimeoutNotification: Failed to make timeout request: method(%s), uri(%s), error(%s)", toReq.Method, toReq.Uri, err.Error()) - util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate) } } - } else if bidder.DebugConfig.TimeoutNotification.Log { + } else if bidder.config.Debug.TimeoutNotification.Log { reqJSON, err := json.Marshal(req) var msg string if err == nil { @@ -412,7 +428,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou } else { msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request marshal failed(%s)", errL[0].Error(), err.Error()) } - util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate) + util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate) } } @@ -422,3 +438,34 @@ type httpCallInfo struct { response *adapters.ResponseData err error } + +// This function adds an httptrace.ClientTrace object to the context so, if connection with the bidder +// endpoint is established, we can keep track of whether the connection was newly created, reused, and +// the time from the connection request, to the connection creation. +func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context { + var connStart, dnsStart time.Time + + trace := &httptrace.ClientTrace{ + // GetConn is called before a connection is created or retrieved from an idle pool + GetConn: func(hostPort string) { + connStart = time.Now() + }, + // GotConn is called after a successful connection is obtained + GotConn: func(info httptrace.GotConnInfo) { + connWaitTime := time.Now().Sub(connStart) + + bidder.me.RecordAdapterConnections(bidder.BidderName, info.Reused, connWaitTime) + }, + // DNSStart is called when a DNS lookup begins. + DNSStart: func(info httptrace.DNSStartInfo) { + dnsStart = time.Now() + }, + // DNSDone is called when a DNS lookup ends. + DNSDone: func(info httptrace.DNSDoneInfo) { + dnsLookupTime := time.Now().Sub(dnsStart) + + bidder.me.RecordDNSTime(dnsLookupTime) + }, + } + return httptrace.WithClientTrace(ctx, trace) +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index d4fc0cf7cd3..1a27b72aa12 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -6,8 +6,11 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "net/http" "net/http/httptest" + "net/http/httptrace" + "strings" "testing" "time" @@ -17,8 +20,11 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbsmetrics" + metricsConf "github.com/prebid/prebid-server/pbsmetrics/config" metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" nativeRequests "github.com/mxmCherry/openrtb/native/request" nativeResponse "github.com/mxmCherry/openrtb/native/response" @@ -68,7 +74,7 @@ func TestSingleBidder(t *testing.T) { }, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) @@ -156,7 +162,7 @@ func TestMultiBidder(t *testing.T) { }}, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) @@ -194,8 +200,10 @@ func TestBidderTimeout(t *testing.T) { defer server.Close() bidder := &bidderAdapter{ - Bidder: &mixedMultiBidder{}, - Client: server.Client(), + Bidder: &mixedMultiBidder{}, + BidderName: openrtb_ext.BidderAppnexus, + Client: server.Client(), + me: &metricsConf.DummyMetricsEngine{}, } callInfo := bidder.doRequest(ctx, &adapters.RequestData{ @@ -235,8 +243,10 @@ func TestConnectionClose(t *testing.T) { server = httptest.NewServer(handler) bidder := &bidderAdapter{ - Bidder: &mixedMultiBidder{}, - Client: server.Client(), + Bidder: &mixedMultiBidder{}, + Client: server.Client(), + BidderName: openrtb_ext.BidderAppnexus, + me: &metricsConf.DummyMetricsEngine{}, } callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{ @@ -514,7 +524,7 @@ func TestMultiCurrencies(t *testing.T) { ) // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -663,7 +673,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() seatBid, errs := bidder.requestBid( context.Background(), @@ -829,7 +839,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -945,7 +955,7 @@ func TestServerCallDebugging(t *testing.T) { Headers: http.Header{}, }, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() bids, _ := bidder.requestBid( @@ -1057,7 +1067,7 @@ func TestMobileNativeTypes(t *testing.T) { }, bidResponse: tc.mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() seatBids, _ := bidder.requestBid( @@ -1078,7 +1088,7 @@ func TestMobileNativeTypes(t *testing.T) { } func TestErrorReporting(t *testing.T) { - bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) currencyConverter := currencies.NewRateConverterDefault() bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if bids != nil { @@ -1233,6 +1243,82 @@ func TestSetAssetTypes(t *testing.T) { } } +func TestCallRecordAdapterConnections(t *testing.T) { + // Setup mock server + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + // declare requestBid parameters + bidAdjustment := 2.0 + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{}, + } + + // setup a mock metrics engine and its expectation + metrics := &pbsmetrics.MetricsEngineMock{} + expectedAdapterName := openrtb_ext.BidderAppnexus + compareConnWaitTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 } + + metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once() + + // Run requestBid using an http.Client with a mock handler + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus) + _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencies.NewRateConverterDefault().Rates(), &adapters.ExtraRequestInfo{}) + + // Assert no errors + assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) + + // Assert RecordAdapterConnections() was called with the parameters we expected + metrics.AssertExpectations(t) +} + +type DNSDoneTripper struct{} + +func (DNSDoneTripper) RoundTrip(req *http.Request) (*http.Response, error) { + //Access the httptrace.ClientTrace + trace := httptrace.ContextClientTrace(req.Context()) + + //Force DNSDone call defined in exchange/bidder.go + trace.DNSDone(httptrace.DNSDoneInfo{}) + + resp := &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Location": {"http://www.example.com/"}}, + Body: ioutil.NopCloser(strings.NewReader("postBody")), + } + return resp, nil +} + +func TestCallRecordRecordDNSTime(t *testing.T) { + // setup a mock metrics engine and its expectation + metricsMock := &pbsmetrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordDNSTime", mock.Anything).Return() + + // Instantiate the bidder that will send the request. We'll make sure to use an + // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) + // gets called + bidder := &bidderAdapter{ + Bidder: &mixedMultiBidder{}, + Client: &http.Client{Transport: DNSDoneTripper{}}, + me: metricsMock, + } + + // Run test + bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}) + + // Tried one or another, none seem to work without panicking + metricsMock.AssertExpectations(t) +} + func TestTimeoutNotificationOff(t *testing.T) { respBody := "{\"bid\":false}" respStatus := 200 @@ -1248,10 +1334,10 @@ func TestTimeoutNotificationOff(t *testing.T) { }, } bidder := &bidderAdapter{ - Bidder: bidderImpl, - Client: server.Client(), - DebugConfig: config.Debug{}, - me: &metricsConfig.DummyMetricsEngine{}, + Bidder: bidderImpl, + Client: server.Client(), + config: bidderAdapterConfig{Debug: config.Debug{}}, + me: &metricsConf.DummyMetricsEngine{}, } if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { t.Error("Failed to cast bidder to a TimeoutBidder") @@ -1284,13 +1370,15 @@ func TestTimeoutNotificationOn(t *testing.T) { bidderAdapter := &bidderAdapter{ Bidder: bidderWrappedWithInfo, Client: server.Client(), - DebugConfig: config.Debug{ - TimeoutNotification: config.TimeoutNotification{ - Log: true, - SamplingRate: 1.0, + config: bidderAdapterConfig{ + Debug: config.Debug{ + TimeoutNotification: config.TimeoutNotification{ + Log: true, + SamplingRate: 1.0, + }, }, }, - me: &metricsConfig.DummyMetricsEngine{}, + me: &metricsConf.DummyMetricsEngine{}, } // Unwrap To Mimic exchange.go Casting Code diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 72de1d4261f..16955e97c5b 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -134,7 +134,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ mockServerURL: mockServerURL, bids: bids, - }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}) + }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) } return adapterMap } diff --git a/go.mod b/go.mod index 72bb9b74886..00cadd31ce1 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/influxdata/influxdb v1.6.1 // indirect github.com/julienschmidt/httprouter v1.1.0 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.0.0 github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect diff --git a/go.sum b/go.sum index 35b2b76591d..5eaf37cad9f 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,11 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index 0dbe9a69d9f..6a36f9e71c0 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -37,7 +37,7 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde } if cfg.Metrics.Prometheus.Port != 0 { // Set up the Prometheus metrics. - returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus) + returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled) engineList = append(engineList, returnEngine.PrometheusMetrics) } @@ -118,6 +118,21 @@ func (me *MultiMetricsEngine) RecordAdapterRequest(labels pbsmetrics.AdapterLabe } } +// Keeps track of created and reused connections to adapter bidders and the time from the +// connection request, to the connection creation, or reuse from the pool across all engines +func (me *MultiMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { + for _, thisME := range *me { + thisME.RecordAdapterConnections(bidderName, connWasReused, connWaitTime) + } +} + +// Times the DNS resolution process +func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { + for _, thisME := range *me { + thisME.RecordDNSTime(dnsLookupTime) + } +} + // RecordAdapterBidReceived across all engines func (me *MultiMetricsEngine) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { for _, thisME := range *me { @@ -237,6 +252,14 @@ func (me *DummyMetricsEngine) RecordAdapterPanic(labels pbsmetrics.AdapterLabels func (me *DummyMetricsEngine) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) { } +// RecordAdapterConnections as a noop +func (me *DummyMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { +} + +// RecordDNSTime as a noop +func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { +} + // RecordAdapterBidReceived as a noop func (me *DummyMetricsEngine) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { } diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index 836434bf25e..26f6ce07b29 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -29,6 +29,7 @@ type Metrics struct { PrebidCacheRequestTimerError metrics.Timer StoredReqCacheMeter map[CacheResult]metrics.Meter StoredImpCacheMeter map[CacheResult]metrics.Meter + DNSLookupTimer metrics.Timer // Metrics for OpenRTB requests specifically. So we can track what % of RequestsMeter are OpenRTB // and know when legacy requests have been abandoned. @@ -81,6 +82,9 @@ type AdapterMetrics struct { BidsReceivedMeter metrics.Meter PanicMeter metrics.Meter MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics + ConnCreated metrics.Counter + ConnReused metrics.Counter + ConnWaitTime metrics.Timer } type MarkupDeliveryMetrics struct { @@ -106,7 +110,7 @@ const unknownBidder openrtb_ext.BidderName = "unknown" // rather than loading legacy metrics that never get filled. // This will also eventually let us configure metrics, such as setting a limited set of metrics // for a production instance, and then expanding again when we need more debugging. -func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableMetrics config.DisabledMetrics) *Metrics { +func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics) *Metrics { blankMeter := &metrics.NilMeter{} blankTimer := &metrics.NilTimer{} @@ -123,6 +127,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa SafariRequestMeter: blankMeter, SafariNoCookieMeter: blankMeter, RequestTimer: blankTimer, + DNSLookupTimer: blankTimer, RequestsQueueTimer: make(map[RequestType]map[bool]metrics.Timer), PrebidCacheRequestTimerSuccess: blankTimer, PrebidCacheRequestTimerError: blankTimer, @@ -153,13 +158,13 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), - MetricsDisabled: disableMetrics, + MetricsDisabled: disabledMetrics, exchanges: exchanges, } for _, a := range exchanges { - newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics() + newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics(newMetrics.MetricsDisabled) } for _, t := range RequestTypes() { @@ -209,6 +214,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.AppRequestMeter = metrics.GetOrRegisterMeter("app_requests", registry) newMetrics.SafariNoCookieMeter = metrics.GetOrRegisterMeter("safari_no_cookie_requests", registry) newMetrics.RequestTimer = metrics.GetOrRegisterTimer("request_time", registry) + newMetrics.DNSLookupTimer = metrics.GetOrRegisterTimer("dns_lookup_time", registry) newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry) newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry) @@ -255,7 +261,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d } // Part of setting up blank metrics, the adapter metrics. -func makeBlankAdapterMetrics() *AdapterMetrics { +func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMetrics { blankMeter := &metrics.NilMeter{} newAdapter := &AdapterMetrics{ NoCookieMeter: blankMeter, @@ -268,6 +274,11 @@ func makeBlankAdapterMetrics() *AdapterMetrics { PanicMeter: blankMeter, MarkupMetrics: makeBlankBidMarkupMetrics(), } + if !disabledMetrics.AdapterConnectionMetrics { + newAdapter.ConnCreated = metrics.NilCounter{} + newAdapter.ConnReused = metrics.NilCounter{} + newAdapter.ConnWaitTime = &metrics.NilTimer{} + } for _, err := range AdapterErrors() { newAdapter.ErrorMeters[err] = blankMeter } @@ -302,6 +313,9 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, openrtb_ext.BidTypeAudio: makeDeliveryMetrics(registry, adapterOrAccount+"."+exchange, openrtb_ext.BidTypeAudio), openrtb_ext.BidTypeNative: makeDeliveryMetrics(registry, adapterOrAccount+"."+exchange, openrtb_ext.BidTypeNative), } + am.ConnCreated = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_created", adapterOrAccount, exchange), registry) + am.ConnReused = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_reused", adapterOrAccount, exchange), registry) + am.ConnWaitTime = metrics.GetOrRegisterTimer(fmt.Sprintf("%[1]s.%[2]s.connection_wait_time", adapterOrAccount, exchange), registry) for err := range am.ErrorMeters { am.ErrorMeters[err] = metrics.GetOrRegisterMeter(fmt.Sprintf("%s.%s.requests.%s", adapterOrAccount, exchange, err), registry) } @@ -348,7 +362,7 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.adapterMetrics = make(map[openrtb_ext.BidderName]*AdapterMetrics, len(me.exchanges)) if !me.MetricsDisabled.AccountAdapterDetails { for _, a := range me.exchanges { - am.adapterMetrics[a] = makeBlankAdapterMetrics() + am.adapterMetrics[a] = makeBlankAdapterMetrics(me.MetricsDisabled) registerAdapterMetrics(me.MetricsRegistry, fmt.Sprintf("account.%s", id), string(a), am.adapterMetrics[a]) } } @@ -472,6 +486,34 @@ func (me *Metrics) RecordAdapterRequest(labels AdapterLabels) { } } +// Keeps track of created and reused connections to adapter bidders and the time from the +// connection request, to the connection creation, or reuse from the pool across all engines +func (me *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, + connWasReused bool, + connWaitTime time.Duration) { + + if me.MetricsDisabled.AdapterConnectionMetrics { + return + } + + am, ok := me.AdapterMetrics[adapterName] + if !ok { + glog.Errorf("Trying to log adapter connection metrics for %s: adapter not found", string(adapterName)) + return + } + + if connWasReused { + am.ConnReused.Inc(1) + } else { + am.ConnCreated.Inc(1) + } + am.ConnWaitTime.Update(connWaitTime) +} + +func (me *Metrics) RecordDNSTime(dnsLookupTime time.Duration) { + me.DNSLookupTimer.Update(dnsLookupTime) +} + // RecordAdapterBidReceived implements a part of the MetricsEngine interface. // This tracks how many bids from each Bidder use `adm` vs. `nurl. func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index 2faa08491e0..f676991649d 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -2,6 +2,7 @@ package pbsmetrics import ( "testing" + "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -115,6 +116,10 @@ func ensureContainsAdapterMetrics(t *testing.T, registry metrics.Registry, name ensureContains(t, registry, name+".request_time", adapterMetrics.RequestTimer) ensureContains(t, registry, name+".prices", adapterMetrics.PriceHistogram) ensureContainsBidTypeMetrics(t, registry, name, adapterMetrics.MarkupMetrics) + + ensureContains(t, registry, name+".connections_created", adapterMetrics.ConnCreated) + ensureContains(t, registry, name+".connections_reused", adapterMetrics.ConnReused) + ensureContains(t, registry, name+".connection_wait_time", adapterMetrics.ConnWaitTime) } func TestRecordBidTypeDisabledConfig(t *testing.T) { @@ -179,6 +184,140 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { } } +func TestRecordDNSTime(t *testing.T) { + testCases := []struct { + description string + inDnsLookupDuration time.Duration + outExpDuration time.Duration + }{ + { + description: "Five second DNS lookup time", + inDnsLookupDuration: time.Second * 5, + outExpDuration: time.Second * 5, + }, + { + description: "Zero DNS lookup time", + inDnsLookupDuration: time.Duration(0), + outExpDuration: time.Duration(0), + }, + } + for _, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) + + m.RecordDNSTime(test.inDnsLookupDuration) + + assert.Equal(t, test.outExpDuration.Nanoseconds(), m.DNSLookupTimer.Sum(), test.description) + } +} + +func TestRecordAdapterConnections(t *testing.T) { + var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + + type testIn struct { + adapterName openrtb_ext.BidderName + connWasReused bool + connWait time.Duration + connMetricsDisabled bool + } + + type testOut struct { + expectedConnReusedCount int64 + expectedConnCreatedCount int64 + expectedConnWaitTime time.Duration + } + + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "Successful, new connection created, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: false, + connWait: time.Second * 5, + connMetricsDisabled: false, + }, + out: testOut{ + expectedConnReusedCount: 0, + expectedConnCreatedCount: 1, + expectedConnWaitTime: time.Second * 5, + }, + }, + { + description: "Successful, new connection created, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: false, + connWait: time.Second * 4, + connMetricsDisabled: false, + }, + out: testOut{ + expectedConnCreatedCount: 1, + expectedConnWaitTime: time.Second * 4, + }, + }, + { + description: "Successful, was reused, no connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: true, + connMetricsDisabled: false, + }, + out: testOut{ + expectedConnReusedCount: 1, + expectedConnWaitTime: 0, + }, + }, + { + description: "Successful, was reused, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: true, + connWait: time.Second * 5, + connMetricsDisabled: false, + }, + out: testOut{ + expectedConnReusedCount: 1, + expectedConnWaitTime: time.Second * 5, + }, + }, + { + description: "Fake bidder, nothing gets updated", + in: testIn{ + adapterName: fakeBidder, + connWasReused: false, + connWait: 0, + connMetricsDisabled: false, + }, + out: testOut{}, + }, + { + description: "Adapter connection metrics are disabled, nothing gets updated", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: false, + connWait: time.Second * 5, + connMetricsDisabled: true, + }, + out: testOut{}, + }, + } + + for i, test := range testCases { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}) + + m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) + + assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i) + assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i) + assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i) + } +} + func TestNewMetricsWithDisabledConfig(t *testing.T) { registry := metrics.NewRegistry() m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index 514fbac1015..8133bc739a0 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -301,6 +301,8 @@ type MetricsEngine interface { RecordLegacyImps(labels Labels, numImps int) // RecordImps for the legacy engine RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status RecordAdapterRequest(labels AdapterLabels) + RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) + RecordDNSTime(dnsLookupTime time.Duration) RecordAdapterPanic(labels AdapterLabels) // This records whether or not a bid of a particular type uses `adm` or `nurl`. // Since the legacy endpoints don't have a bid type, it can only count bids from OpenRTB and AMP. diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index 6c263f0af4d..42a2d1b4c8f 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -52,6 +52,16 @@ func (me *MetricsEngineMock) RecordAdapterRequest(labels AdapterLabels) { me.Called(labels) } +// RecordAdapterConnections mock +func (me *MetricsEngineMock) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { + me.Called(bidderName, connWasReused, connWaitTime) +} + +// RecordDNSTime mock +func (me *MetricsEngineMock) RecordDNSTime(dnsLookupTime time.Duration) { + me.Called(dnsLookupTime) +} + // RecordAdapterBidReceived mock func (me *MetricsEngineMock) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { me.Called(labels, bidType, hasAdm) diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go index ef1d300c4df..4f62a18aae9 100644 --- a/pbsmetrics/prometheus/preload.go +++ b/pbsmetrics/prometheus/preload.go @@ -85,6 +85,20 @@ func preloadLabelValues(m *Metrics) { hasBidsLabel: boolValues, }) + if !m.metricsDisabled.AdapterConnectionMetrics { + preloadLabelValuesForCounter(m.adapterCreatedConnections, map[string][]string{ + adapterLabel: adapterValues, + }) + + preloadLabelValuesForCounter(m.adapterReusedConnections, map[string][]string{ + adapterLabel: adapterValues, + }) + + preloadLabelValuesForHistogram(m.adapterConnectionWaitTime, map[string][]string{ + adapterLabel: adapterValues, + }) + } + preloadLabelValuesForHistogram(m.adapterRequestsTimer, map[string][]string{ adapterLabel: adapterValues, }) diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index d94c4d78f62..b42399b2a62 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -29,23 +29,29 @@ type Metrics struct { storedImpressionsCacheResult *prometheus.CounterVec storedRequestCacheResult *prometheus.CounterVec timeoutNotifications *prometheus.CounterVec + dnsLookupTimer prometheus.Histogram privacyCCPA *prometheus.CounterVec privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec privacyTCF *prometheus.CounterVec // Adapter Metrics - adapterBids *prometheus.CounterVec - adapterCookieSync *prometheus.CounterVec - adapterErrors *prometheus.CounterVec - adapterPanics *prometheus.CounterVec - adapterPrices *prometheus.HistogramVec - adapterRequests *prometheus.CounterVec - adapterRequestsTimer *prometheus.HistogramVec - adapterUserSync *prometheus.CounterVec + adapterBids *prometheus.CounterVec + adapterCookieSync *prometheus.CounterVec + adapterErrors *prometheus.CounterVec + adapterPanics *prometheus.CounterVec + adapterPrices *prometheus.HistogramVec + adapterRequests *prometheus.CounterVec + adapterRequestsTimer *prometheus.HistogramVec + adapterUserSync *prometheus.CounterVec + adapterReusedConnections *prometheus.CounterVec + adapterCreatedConnections *prometheus.CounterVec + adapterConnectionWaitTime *prometheus.HistogramVec // Account Metrics accountRequests *prometheus.CounterVec + + metricsDisabled config.DisabledMetrics } const ( @@ -97,14 +103,15 @@ const ( ) // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. -func NewMetrics(cfg config.PrometheusMetrics) *Metrics { - requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} +func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics) *Metrics { + standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300} metrics := Metrics{} metrics.Registry = prometheus.NewRegistry() + metrics.metricsDisabled = disabledMetrics metrics.connectionsClosed = newCounterWithoutLabels(cfg, metrics.Registry, "connections_closed", @@ -132,7 +139,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "impressions_requests_legacy", "Count of requested impressions to Prebid Server using the legacy endpoint.") - metrics.prebidCacheWriteTimer = newHistogram(cfg, metrics.Registry, + metrics.prebidCacheWriteTimer = newHistogramVec(cfg, metrics.Registry, "prebidcache_write_time_seconds", "Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.", []string{successLabel}, @@ -143,11 +150,11 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of total requests to Prebid Server labeled by type and status.", []string{requestTypeLabel, requestStatusLabel}) - metrics.requestsTimer = newHistogram(cfg, metrics.Registry, + metrics.requestsTimer = newHistogramVec(cfg, metrics.Registry, "request_time_seconds", "Seconds to resolve successful Prebid Server requests labeled by type.", []string{requestTypeLabel}, - requestTimeBuckets) + standardTimeBuckets) metrics.requestsWithoutCookie = newCounter(cfg, metrics.Registry, "requests_without_cookie", @@ -169,6 +176,11 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of timeout notifications triggered, and if they were successfully sent.", []string{successLabel}) + metrics.dnsLookupTimer = newHistogram(cfg, metrics.Registry, + "dns_lookup_time", + "Seconds to resolve DNS", + standardTimeBuckets) + metrics.privacyCCPA = newCounter(cfg, metrics.Registry, "privacy_ccpa", "Count of total requests to Prebid Server where CCPA was provided by source and opt-out .", @@ -209,7 +221,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of panics labeled by adapter.", []string{adapterLabel}) - metrics.adapterPrices = newHistogram(cfg, metrics.Registry, + metrics.adapterPrices = newHistogramVec(cfg, metrics.Registry, "adapter_prices", "Monetary value of the bids labeled by adapter.", []string{adapterLabel}, @@ -220,11 +232,29 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of requests labeled by adapter, if has a cookie, and if it resulted in bids.", []string{adapterLabel, cookieLabel, hasBidsLabel}) - metrics.adapterRequestsTimer = newHistogram(cfg, metrics.Registry, + if !metrics.metricsDisabled.AdapterConnectionMetrics { + metrics.adapterCreatedConnections = newCounter(cfg, metrics.Registry, + "adapter_connection_created", + "Count that keeps track of new connections when contacting adapter bidder endpoints.", + []string{adapterLabel}) + + metrics.adapterReusedConnections = newCounter(cfg, metrics.Registry, + "adapter_connection_reused", + "Count that keeps track of reused connections when contacting adapter bidder endpoints.", + []string{adapterLabel}) + + metrics.adapterConnectionWaitTime = newHistogramVec(cfg, metrics.Registry, + "adapter_connection_wait", + "Seconds from when the connection was requested until it is either created or reused", + []string{adapterLabel}, + standardTimeBuckets) + } + + metrics.adapterRequestsTimer = newHistogramVec(cfg, metrics.Registry, "adapter_request_time_seconds", "Seconds to resolve each successful request labeled by adapter.", []string{adapterLabel}, - requestTimeBuckets) + standardTimeBuckets) metrics.adapterUserSync = newCounter(cfg, metrics.Registry, "adapter_user_sync", @@ -236,7 +266,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics { "Count of total requests to Prebid Server labeled by account.", []string{accountLabel}) - metrics.requestsQueueTimer = newHistogram(cfg, metrics.Registry, + metrics.requestsQueueTimer = newHistogramVec(cfg, metrics.Registry, "request_queue_time", "Seconds request was waiting in queue", []string{requestTypeLabel, requestStatusLabel}, @@ -271,7 +301,7 @@ func newCounterWithoutLabels(cfg config.PrometheusMetrics, registry *prometheus. return counter } -func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec { +func newHistogramVec(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec { opts := prometheus.HistogramOpts{ Namespace: cfg.Namespace, Subsystem: cfg.Subsystem, @@ -284,6 +314,19 @@ func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, n return histogram } +func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, buckets []float64) prometheus.Histogram { + opts := prometheus.HistogramOpts{ + Namespace: cfg.Namespace, + Subsystem: cfg.Subsystem, + Name: name, + Help: help, + Buckets: buckets, + } + histogram := prometheus.NewHistogram(opts) + registry.MustRegister(histogram) + return histogram +} + func (m *Metrics) RecordConnectionAccept(success bool) { if success { m.connectionsOpened.Inc() @@ -359,6 +402,32 @@ func (m *Metrics) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) { } } +// Keeps track of created and reused connections to adapter bidders and the time from the +// connection request, to the connection creation, or reuse from the pool across all engines +func (m *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { + if m.metricsDisabled.AdapterConnectionMetrics { + return + } + + if connWasReused { + m.adapterReusedConnections.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Inc() + } else { + m.adapterCreatedConnections.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Inc() + } + + m.adapterConnectionWaitTime.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Observe(connWaitTime.Seconds()) +} + +func (m *Metrics) RecordDNSTime(dnsLookupTime time.Duration) { + m.dnsLookupTimer.Observe(dnsLookupTime.Seconds()) +} + func (m *Metrics) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) { m.adapterPanics.With(prometheus.Labels{ adapterLabel: string(labels.Adapter), diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index b722ab28b5c..b6153b16278 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -1,6 +1,7 @@ package prometheusmetrics import ( + "fmt" "testing" "time" @@ -17,7 +18,7 @@ func createMetricsForTesting() *Metrics { Port: 8080, Namespace: "prebid", Subsystem: "server", - }) + }, config.DisabledMetrics{}) } func TestMetricCountGatekeeping(t *testing.T) { @@ -61,7 +62,7 @@ func TestMetricCountGatekeeping(t *testing.T) { // Verify Per-Adapter Cardinality // - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the // cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics. - assert.True(t, perAdapterCardinalityCount <= 22, "Per-Adapter Cardinality") + assert.True(t, perAdapterCardinalityCount <= 27, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) } func TestConnectionMetrics(t *testing.T) { @@ -944,6 +945,175 @@ func TestTimeoutNotifications(t *testing.T) { } +func TestRecordDNSTime(t *testing.T) { + type testIn struct { + dnsLookupDuration time.Duration + } + type testOut struct { + expDuration float64 + expCount uint64 + } + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "Five second DNS lookup time", + in: testIn{ + dnsLookupDuration: time.Second * 5, + }, + out: testOut{ + expDuration: 5, + expCount: 1, + }, + }, + { + description: "Zero DNS lookup time", + in: testIn{}, + out: testOut{ + expDuration: 0, + expCount: 1, + }, + }, + } + for i, test := range testCases { + pm := createMetricsForTesting() + pm.RecordDNSTime(test.in.dnsLookupDuration) + + m := dto.Metric{} + pm.dnsLookupTimer.Write(&m) + histogram := *m.GetHistogram() + + assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) + assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) + } +} + +func TestRecordAdapterConnections(t *testing.T) { + + type testIn struct { + adapterName openrtb_ext.BidderName + connWasReused bool + connWait time.Duration + } + + type testOut struct { + expectedConnReusedCount int64 + expectedConnCreatedCount int64 + expectedConnWaitCount uint64 + expectedConnWaitTime float64 + } + + testCases := []struct { + description string + in testIn + out testOut + }{ + { + description: "[1] Successful, new connection created, was idle, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: false, + connWait: time.Second * 5, + }, + out: testOut{ + expectedConnReusedCount: 0, + expectedConnCreatedCount: 1, + expectedConnWaitCount: 1, + expectedConnWaitTime: 5, + }, + }, + { + description: "[2] Successful, new connection created, not idle, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: false, + connWait: time.Second * 4, + }, + out: testOut{ + expectedConnReusedCount: 0, + expectedConnCreatedCount: 1, + expectedConnWaitCount: 1, + expectedConnWaitTime: 4, + }, + }, + { + description: "[3] Successful, was reused, was idle, no connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: true, + }, + out: testOut{ + expectedConnReusedCount: 1, + expectedConnCreatedCount: 0, + expectedConnWaitCount: 1, + expectedConnWaitTime: 0, + }, + }, + { + description: "[4] Successful, was reused, not idle, has connection wait", + in: testIn{ + adapterName: openrtb_ext.BidderAppnexus, + connWasReused: true, + connWait: time.Second * 5, + }, + out: testOut{ + expectedConnReusedCount: 1, + expectedConnCreatedCount: 0, + expectedConnWaitCount: 1, + expectedConnWaitTime: 5, + }, + }, + } + + for i, test := range testCases { + m := createMetricsForTesting() + assertDesciptions := []string{ + fmt.Sprintf("[%d] Metric: adapterReusedConnections; Desc: %s", i+1, test.description), + fmt.Sprintf("[%d] Metric: adapterCreatedConnections; Desc: %s", i+1, test.description), + fmt.Sprintf("[%d] Metric: adapterWaitConnectionCount; Desc: %s", i+1, test.description), + fmt.Sprintf("[%d] Metric: adapterWaitConnectionTime; Desc: %s", i+1, test.description), + } + + m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) + + // Assert number of reused connections + assertCounterVecValue(t, + assertDesciptions[0], + "adapter_connection_reused", + m.adapterReusedConnections, + float64(test.out.expectedConnReusedCount), + prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + + // Assert number of new created connections + assertCounterVecValue(t, + assertDesciptions[1], + "adapter_connection_created", + m.adapterCreatedConnections, + float64(test.out.expectedConnCreatedCount), + prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + + // Assert connection wait time + histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, string(test.in.adapterName)) + assert.Equal(t, test.out.expectedConnWaitCount, histogram.GetSampleCount(), assertDesciptions[2]) + assert.Equal(t, test.out.expectedConnWaitTime, histogram.GetSampleSum(), assertDesciptions[3]) + } +} + +func TestDisableAdapterConnections(t *testing.T) { + prometheusMetrics := NewMetrics(config.PrometheusMetrics{ + Port: 8080, + Namespace: "prebid", + Subsystem: "server", + }, config.DisabledMetrics{AdapterConnectionMetrics: true}) + + // Assert counter vector was not initialized + assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") + assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") + assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") +} + func TestRecordRequestPrivacy(t *testing.T) { m := createMetricsForTesting() From f1582a494e407635544be416632e26c76d2b1881 Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Mon, 27 Jul 2020 18:53:23 +0530 Subject: [PATCH 150/603] Pubmatic: Support for video duration and primary category (#1384) * Adding suport for video duration and primary category in pubmatic adapter * Adding code review changes for PR-1384 * Adding changes for syntaxNode suggestion Co-authored-by: Isha Bharti --- adapters/pubmatic/pubmatic.go | 67 ++++++++++++------- adapters/pubmatic/pubmatic_test.go | 33 ++++----- .../pubmatictest/exemplary/video.json | 21 ++++-- 3 files changed, 78 insertions(+), 43 deletions(-) diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index aae115386d0..795655048b4 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -21,7 +21,6 @@ import ( ) const MAX_IMPRESSIONS_PUBMATIC = 30 -const bidTypeExtKey = "BidType" type PubmaticAdapter struct { http *adapters.HTTPAdapter @@ -48,6 +47,15 @@ type pubmaticParams struct { Keywords map[string]string `json:"keywords,omitempty"` } +type pubmaticBidExtVideo struct { + Duration *int `json:"duration,omitempty"` +} + +type pubmaticBidExt struct { + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` +} + const ( INVALID_PARAMS = "Invalid BidParam" MISSING_PUBID = "Missing PubID" @@ -289,7 +297,11 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder DealId: bid.DealID, } - mediaType := getBidType(bid.Ext) + var bidExt pubmaticBidExt + mediaType := openrtb_ext.BidTypeBanner + if err := json.Unmarshal(bid.Ext, &bidExt); err == nil { + mediaType = getBidType(&bidExt) + } pbid.CreativeMediaType = string(mediaType) bids = append(bids, &pbid) @@ -549,9 +561,24 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external for _, sb := range bidResp.SeatBid { for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] + impVideo := &openrtb_ext.ExtBidPrebidVideo{} + + if len(bid.Cat) > 1 { + bid.Cat = bid.Cat[0:1] + } + + var bidExt *pubmaticBidExt + bidType := openrtb_ext.BidTypeBanner + if err := json.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt != nil { + if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil { + impVideo.Duration = *bidExt.VideoCreativeInfo.Duration + } + bidType = getBidType(bidExt) + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: getBidType(bid.Ext), + Bid: &bid, + BidType: bidType, + BidVideo: impVideo, }) } @@ -560,28 +587,20 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } // getBidType returns the bid type specified in the response bid.ext -func getBidType(bidExt json.RawMessage) openrtb_ext.BidType { +func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType { // setting "banner" as the default bid type bidType := openrtb_ext.BidTypeBanner - if bidExt != nil { - bidExtMap := make(map[string]interface{}) - extbyte, err := json.Marshal(bidExt) - if err == nil { - err = json.Unmarshal(extbyte, &bidExtMap) - if err == nil && bidExtMap[bidTypeExtKey] != nil { - bidTypeVal := int(bidExtMap[bidTypeExtKey].(float64)) - switch bidTypeVal { - case 0: - bidType = openrtb_ext.BidTypeBanner - case 1: - bidType = openrtb_ext.BidTypeVideo - case 2: - bidType = openrtb_ext.BidTypeNative - default: - // default value is banner - bidType = openrtb_ext.BidTypeBanner - } - } + if bidExt != nil && bidExt.BidType != nil { + switch *bidExt.BidType { + case 0: + bidType = openrtb_ext.BidTypeBanner + case 1: + bidType = openrtb_ext.BidTypeVideo + case 2: + bidType = openrtb_ext.BidTypeNative + default: + // default value is banner + bidType = openrtb_ext.BidTypeBanner } } return bidType diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index be086f5adf1..5dd236d9742 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -674,18 +674,18 @@ func TestPubmaticSampleRequest(t *testing.T) { } func TestGetBidTypeVideo(t *testing.T) { - extJSON := `{"BidType":1}` - extrm := json.RawMessage(extJSON) - actualBidTypeValue := getBidType(extrm) + pubmaticExt := new(pubmaticBidExt) + pubmaticExt.BidType = new(int) + *pubmaticExt.BidType = 1 + actualBidTypeValue := getBidType(pubmaticExt) if actualBidTypeValue != openrtb_ext.BidTypeVideo { t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeVideo, actualBidTypeValue) } } func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { - extJSON := `{}` - extrm := json.RawMessage(extJSON) - actualBidTypeValue := getBidType(extrm) + pubmaticExt := pubmaticBidExt{} + actualBidTypeValue := getBidType(&pubmaticExt) // banner is the default bid type when no bidType key is present in the bid.ext if actualBidTypeValue != "banner" { t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue) @@ -693,27 +693,30 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { } func TestGetBidTypeBanner(t *testing.T) { - extJSON := `{"BidType":0}` - extrm := json.RawMessage(extJSON) - actualBidTypeValue := getBidType(extrm) + pubmaticExt := new(pubmaticBidExt) + pubmaticExt.BidType = new(int) + *pubmaticExt.BidType = 0 + actualBidTypeValue := getBidType(pubmaticExt) if actualBidTypeValue != openrtb_ext.BidTypeBanner { t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue) } } func TestGetBidTypeNative(t *testing.T) { - extJSON := `{"BidType":2}` - extrm := json.RawMessage(extJSON) - actualBidTypeValue := getBidType(extrm) + pubmaticExt := new(pubmaticBidExt) + pubmaticExt.BidType = new(int) + *pubmaticExt.BidType = 2 + actualBidTypeValue := getBidType(pubmaticExt) if actualBidTypeValue != openrtb_ext.BidTypeNative { t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeNative, actualBidTypeValue) } } func TestGetBidTypeForUnsupportedCode(t *testing.T) { - extJSON := `{"BidType":99}` - extrm := json.RawMessage(extJSON) - actualBidTypeValue := getBidType(extrm) + pubmaticExt := new(pubmaticBidExt) + pubmaticExt.BidType = new(int) + *pubmaticExt.BidType = 99 + actualBidTypeValue := getBidType(pubmaticExt) if actualBidTypeValue != openrtb_ext.BidTypeBanner { t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue) } diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json index 25ed366ee05..4c874535a35 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/video.json +++ b/adapters/pubmatic/pubmatictest/exemplary/video.json @@ -112,10 +112,14 @@ "h": 250, "w": 300, "dealid":"test deal", + "cat" : ["IAB-1", "IAB-2"], "ext": { "dspid": 6, "deal_channel": 1, - "BidType": 1 + "BidType": 1, + "video" : { + "duration" : 5 + } } }] } @@ -139,6 +143,9 @@ "adid": "29681110", "adm": "some-test-ad", "adomain": ["pubmatic.com"], + "cat": [ + "IAB-1" + ], "crid": "29681110", "w": 300, "h": 250, @@ -146,12 +153,18 @@ "ext": { "dspid": 6, "deal_channel": 1, - "BidType": 1 + "BidType": 1, + "video" : { + "duration" : 5 + } } }, - "type": "video" + "type": "video", + "video" :{ + "duration" : 5 + } } ] } ] - } \ No newline at end of file + } From a857e6868e0de3445dc0c65cf6522d290ce1df23 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 29 Jul 2020 12:03:31 -0400 Subject: [PATCH 151/603] Add IPv6 Non-Public Network (#1417) --- config/config.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index a82dbb5edf7..8545523d238 100755 --- a/config/config.go +++ b/config/config.go @@ -896,13 +896,14 @@ func SetupViper(v *viper.Viper, filename string) { /* Loopback: 127.0.0.0/8 /* /* IPv6 - /* Loopback: ::1/128 - /* Unique Local: fc00::/7 - /* Link Local: fe80::/10 - /* Multicast: ff00::/8 + /* Loopback: ::1/128 + /* Documentation: 2001:db8::/32 + /* Unique Local: fc00::/7 + /* Link Local: fe80::/10 + /* Multicast: ff00::/8 */ v.SetDefault("request_validation.ipv4_private_networks", []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}) - v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8"}) + v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "2001:db8::/32"}) // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) From 3fa47ab2218a1fdcb6d9a6fc5edd3fff5ad10318 Mon Sep 17 00:00:00 2001 From: susyt Date: Wed, 29 Jul 2020 14:18:29 -0700 Subject: [PATCH 152/603] GumGum: adds support for video (#1408) --- adapters/gumgum/gumgum.go | 57 ++++++++-- .../gumgum/gumgumtest/exemplary/video.json | 106 ++++++++++++++++++ .../gumgum/gumgumtest/params/race/video.json | 3 + .../supplemental/missing-video-params.json | 36 ++++++ static/bidder-info/gumgum.yaml | 1 + 5 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 adapters/gumgum/gumgumtest/exemplary/video.json create mode 100644 adapters/gumgum/gumgumtest/params/race/video.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/missing-video-params.json diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 84a008d1891..490979091a4 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -8,12 +8,16 @@ import ( "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "net/http" + "strconv" + "strings" ) +// GumGumAdapter implements Bidder interface. type GumGumAdapter struct { URI string } +// MakeRequests makes the HTTP requests which should be made to fetch bids. func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var validImps []openrtb.Imp var trackingId string @@ -26,15 +30,21 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt zone, err := preprocess(&imp) if err != nil { errs = append(errs, err) - } else { - if request.Imp[i].Banner != nil { - bannerCopy := *request.Imp[i].Banner - if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { - format := bannerCopy.Format[0] - bannerCopy.W = &(format.W) - bannerCopy.H = &(format.H) - } - request.Imp[i].Banner = &bannerCopy + } else if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } else if request.Imp[i].Video != nil { + err := validateVideoParams(request.Imp[i].Video) + if err != nil { + errs = append(errs, err) + } else { validImps = append(validImps, request.Imp[i]) trackingId = zone } @@ -70,6 +80,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } +// MakeBids unpacks the server's response into Bids. func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -98,12 +109,19 @@ func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) + if mediaType == openrtb_ext.BidTypeVideo { + price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) + sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: openrtb_ext.BidTypeBanner, + BidType: mediaType, }) } } + return bidResponse, errs } @@ -128,6 +146,25 @@ func preprocess(imp *openrtb.Imp) (string, error) { return zone, nil } +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} + +func validateVideoParams(video *openrtb.Video) (err error) { + if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { + return &errortypes.BadInput{ + Message: "Invalid or missing video field(s)", + } + } + return nil +} + +// NewGumGumBidder configures bidder endpoint. func NewGumGumBidder(endpoint string) *GumGumAdapter { return &GumGumAdapter{ URI: endpoint, diff --git a/adapters/gumgum/gumgumtest/exemplary/video.json b/adapters/gumgum/gumgumtest/exemplary/video.json new file mode 100644 index 00000000000..ea76c733f34 --- /dev/null +++ b/adapters/gumgum/gumgumtest/exemplary/video.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2 + ], + "w": 640, + "h": 480, + "startdelay": 1, + "placement": 1, + "linearity": 1 + }, + "ext": { + "bidder": { + "zone": "ggumtest" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://g2.gumgum.com/providers/prbds2s/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2 + ], + "w": 640, + "h": 480, + "startdelay": 1, + "placement": 1, + "linearity": 1 + }, + "ext": { + "bidder": { + "zone": "ggumtest" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "id": "15da721e-940a-4db6-8621-a1f93140b21b", + "impid": "video1", + "price": 15, + "adid": "59082", + "adm": "\n \n \n GumGum Video\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n", + "cid": "3579", + "crid": "59082" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "15da721e-940a-4db6-8621-a1f93140b21b", + "impid": "video1", + "price": 15, + "adid": "59082", + "adm": "\n \n \n GumGum Video\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n", + "cid": "3579", + "crid": "59082" + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gumgum/gumgumtest/params/race/video.json b/adapters/gumgum/gumgumtest/params/race/video.json new file mode 100644 index 00000000000..3ed284384d3 --- /dev/null +++ b/adapters/gumgum/gumgumtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} \ No newline at end of file diff --git a/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json b/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json new file mode 100644 index 00000000000..b2475cd7bb4 --- /dev/null +++ b/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1, + 2 + ], + "w": 640, + "h": 480, + "startdelay": 1, + "placement": 1, + "linearity": 1 + }, + "ext": { + "bidder": { + "zone": "ggumtest" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or missing video field(s)", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index 0feca7cdf73..6ba563b4c1c 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -4,3 +4,4 @@ capabilities: site: mediaTypes: - banner + - video \ No newline at end of file From 551faadb0502cef9d7dda5b90655f348b3a7bc19 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Thu, 30 Jul 2020 09:23:27 -0700 Subject: [PATCH 153/603] OpenX adapter: pass optional platform (PBID-598) (#1421) --- adapters/openx/openx.go | 4 +++- .../openxtest/exemplary/optional-params.json | 4 +++- docs/bidders/openx.md | 7 ++++-- openrtb_ext/imp_openx.go | 1 + static/bidder-params/openx.json | 22 +++++++++++++++++-- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index ca88b18bdb8..c2a42adf295 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -22,7 +22,8 @@ type openxImpExt struct { } type openxReqExt struct { - DelDomain string `json:"delDomain"` + DelDomain string `json:"delDomain,omitempty"` + Platform string `json:"platform,omitempty"` BidderConfig string `json:"bc"` } @@ -125,6 +126,7 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { } reqExt.DelDomain = openxExt.DelDomain + reqExt.Platform = openxExt.Platform imp.TagID = openxExt.Unit imp.BidFloor = openxExt.CustomFloor diff --git a/adapters/openx/openxtest/exemplary/optional-params.json b/adapters/openx/openxtest/exemplary/optional-params.json index 225559875a8..e8fcf394b8b 100644 --- a/adapters/openx/openxtest/exemplary/optional-params.json +++ b/adapters/openx/openxtest/exemplary/optional-params.json @@ -11,6 +11,7 @@ "bidder": { "unit": "539439964", "delDomain": "se-demo-d.openx.net", + "platform": "PLATFORM", "customFloor": 0.1, "customParams": {"foo": "bar"} } @@ -40,7 +41,8 @@ ], "ext": { "bc": "hb_pbs_1.0.0", - "delDomain": "se-demo-d.openx.net" + "delDomain": "se-demo-d.openx.net", + "platform": "PLATFORM" } } }, diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md index c366db3ab61..54a0a5b1e72 100644 --- a/docs/bidders/openx.md +++ b/docs/bidders/openx.md @@ -5,10 +5,13 @@ OpenX supports the following parameters: | property | type | required? | description | example | |----------|------|-----------|-------------|---------| | unit | string | required | The ad unit id | "10092842" | -| delDomain | string | required | The delivery domain for the customer | "sademo-d.openx.net" | +| delDomain | string | required\* | The delivery domain for the customer | "sademo-d.openx.net" | +| platform | uuid | required\* | The platform id for the customer | "a3aece0c-9e80-4316-8deb-faf804779bd1" | | customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | | customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | +\* At least one of `delDomain` or `platform` parameters is required. + If you have any questions regarding setting up, please reach out to your account manager or @@ -59,4 +62,4 @@ If you have any questions regarding setting up, please reach out to your account }, } } -``` \ No newline at end of file +``` diff --git a/openrtb_ext/imp_openx.go b/openrtb_ext/imp_openx.go index e63595b0912..2625cb3802d 100644 --- a/openrtb_ext/imp_openx.go +++ b/openrtb_ext/imp_openx.go @@ -3,6 +3,7 @@ package openrtb_ext // ExtImpOpenx defines the contract for bidrequest.imp[i].ext.openx type ExtImpOpenx struct { Unit string `json:"unit"` + Platform string `json:"platform"` DelDomain string `json:"delDomain"` CustomFloor float64 `json:"customFloor"` CustomParams map[string]interface{} `json:"customParams"` diff --git a/static/bidder-params/openx.json b/static/bidder-params/openx.json index 93a672ed629..6dbd10178e4 100644 --- a/static/bidder-params/openx.json +++ b/static/bidder-params/openx.json @@ -16,6 +16,11 @@ "pattern": "\\.[a-zA-Z]{2,3}$", "format": "hostname" }, + "platform": { + "type": "string", + "description": "The platform id for the customer.", + "format": "uuid" + }, "customFloor": { "type": "number", "description": "The minimum CPM price in USD.", @@ -26,6 +31,19 @@ "description": "User-defined targeting key-value pairs." } }, - - "required": ["unit", "delDomain"] + "required": [ + "unit" + ], + "anyOf": [ + { + "required": [ + "delDomain" + ] + }, + { + "required": [ + "platform" + ] + } + ] } From 126455ccfeb3965b5c4a8b49749dbbce144e5317 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 30 Jul 2020 12:41:26 -0400 Subject: [PATCH 154/603] Adds keyvalue hb_format support (#1414) --- docs/endpoints/openrtb2/auction.md | 3 + exchange/exchange.go | 1 + exchange/targeting.go | 4 + exchange/targeting_test.go | 209 +++++++++++++++++++++++++++++ openrtb_ext/bid.go | 3 + openrtb_ext/request.go | 1 + 6 files changed, 221 insertions(+) diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md index d09216188b8..b532923e793 100644 --- a/docs/endpoints/openrtb2/auction.md +++ b/docs/endpoints/openrtb2/auction.md @@ -173,6 +173,7 @@ to set these params on the response at `response.seatbid[i].bid[j].ext.prebid.ta }, "includewinners": false, // Optional param defaulting to true "includebidderkeys": false // Optional param defaulting to true + "includeformat": false // Optional param defaulting to false } } } @@ -184,6 +185,8 @@ For backwards compatibility the following strings will also be allowed as price One of "includewinners" or "includebidderkeys" must be true (both default to true if unset). If both were false, then no targeting keys would be set, which is better configured by omitting targeting altogether. +The parameter "includeformat" indicates the type of the bid (banner, video, etc) for multiformat requests. It will add the key `hb_format` and/or `hb_format_{bidderName}` as per "includewinners" and "includebidderkeys" above. + MediaType PriceGranularity (PBS-Java only) - when a single OpenRTB request contains multiple impressions with different mediatypes, or a single impression supports multiple formats, the different mediatypes may need different price granularities. If `mediatypepricegranularity` is present, `pricegranularity` would only be used for any mediatypes not specified. ``` diff --git a/exchange/exchange.go b/exchange/exchange.go index 3f0258dd3c1..5001e495440 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -135,6 +135,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque includeBidderKeys: requestExt.Prebid.Targeting.IncludeBidderKeys, includeCacheBids: shouldCacheBids, includeCacheVast: shouldCacheVAST, + includeFormat: requestExt.Prebid.Targeting.IncludeFormat, } targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() } diff --git a/exchange/targeting.go b/exchange/targeting.go index 994ad9e7c81..dca57653b44 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -22,6 +22,7 @@ type targetData struct { includeBidderKeys bool includeCacheBids bool includeCacheVast bool + includeFormat bool // cacheHost and cachePath exist to supply cache host and path as targeting parameters cacheHost string cachePath string @@ -53,6 +54,9 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi if vastID, ok := auc.vastCacheIds[topBidPerBidder.bid]; ok { targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner) } + if targData.includeFormat { + targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner) + } if targData.cacheHost != "" { targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, bidderName, isOverallWinner) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 16955e97c5b..11b60db01f3 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -247,3 +247,212 @@ func (f *mockFetcher) GetId(bidder openrtb_ext.BidderName) (string, bool) { func mockServer(w http.ResponseWriter, req *http.Request) { w.Write([]byte("{}")) } + +type TargetingTestData struct { + Description string + TargetData targetData + Auction auction + IsApp bool + CategoryMapping map[string]string + ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string +} + +var bid123 *openrtb.Bid = &openrtb.Bid{ + Price: 1.23, +} + +var bid111 *openrtb.Bid = &openrtb.Bid{ + Price: 1.11, + DealID: "mydeal", +} +var bid084 *openrtb.Bid = &openrtb.Bid{ + Price: 0.84, +} + +var TargetingTests []TargetingTestData = []TargetingTestData{ + { + Description: "Targeting winners only (most basic targeting example)", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeWinners: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder": "appnexus", + "hb_pb": "1.20", + }, + openrtb_ext.BidderRubicon: {}, + }, + }, + }, + { + Description: "Targeting on bidders only", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeBidderKeys: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder_appnexus": "appnexus", + "hb_pb_appnexus": "1.20", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "0.80", + }, + }, + }, + }, + { + Description: "Full basic targeting with hd_format", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeWinners: true, + includeBidderKeys: true, + includeFormat: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_pb": "1.20", + "hb_pb_appnexus": "1.20", + "hb_format": "banner", + "hb_format_appnexus": "banner", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "0.80", + "hb_format_rubicon": "banner", + }, + }, + }, + }, + { + Description: "Cache and deal targeting test", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeBidderKeys: true, + cacheHost: "cache.prebid.com", + cachePath: "cache", + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid111, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + cacheIds: map[*openrtb.Bid]string{ + bid123: "55555", + bid111: "cacheme", + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder_appnexus": "appnexus", + "hb_pb_appnexus": "1.20", + "hb_cache_id_appnexus": "55555", + "hb_cache_host_appnex": "cache.prebid.com", + "hb_cache_path_appnex": "cache", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "1.10", + "hb_cache_id_rubicon": "cacheme", + "hb_deal_rubicon": "mydeal", + "hb_cache_host_rubico": "cache.prebid.com", + "hb_cache_path_rubico": "cache", + }, + }, + }, + }, +} + +func TestSetTargeting(t *testing.T) { + for _, test := range TargetingTests { + auc := &test.Auction + // Set rounded prices from the auction data + auc.setRoundedPrices(test.TargetData.priceGranularity) + winningBids := make(map[string]*pbsOrtbBid) + // Set winning bids from the auction data + for imp, bidsByBidder := range auc.winningBidsByBidder { + for _, bid := range bidsByBidder { + if winningBid, ok := winningBids[imp]; ok { + if winningBid.bid.Price < bid.bid.Price { + winningBids[imp] = bid + } + } else { + winningBids[imp] = bid + } + } + } + auc.winningBids = winningBids + targData := test.TargetData + targData.setTargeting(auc, test.IsApp, test.CategoryMapping) + for imp, targetsByBidder := range test.ExpectedBidTargetsByBidder { + for bidder, expected := range targetsByBidder { + assert.Equal(t, + expected, + auc.winningBidsByBidder[imp][bidder].bidTargets, + "Test: %s\nTargeting failed for bidder %s on imp %s.", + test.Description, + string(bidder), + imp) + } + } + } + +} diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 3b297c7ab5d..09ee375be82 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -99,6 +99,9 @@ const ( HbSizeConstantKey TargetingKey = "hb_size" HbDealIDConstantKey TargetingKey = "hb_deal" + // HbFormatKey is the format of the bid. For example, "video", "banner" + HbFormatKey TargetingKey = "hb_format" + // HbCacheKey and HbVastCacheKey store UUIDs which can be used to fetch things from prebid cache. // Callers should *never* assume that either of these exist, since the call to the cache may always fail. // diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 86388f60cf4..acfd4a1e71f 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -85,6 +85,7 @@ type ExtRequestTargeting struct { IncludeWinners bool `json:"includewinners"` IncludeBidderKeys bool `json:"includebidderkeys"` IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory"` + IncludeFormat bool `json:"includeformat"` DurationRangeSec []int `json:"durationrangesec"` } From deb19c39420a9315a8838d52819cffd7479a48d6 Mon Sep 17 00:00:00 2001 From: gpolaert Date: Mon, 3 Aug 2020 19:38:59 +0200 Subject: [PATCH 155/603] feat: Add new logger module - Pubstack Analytics Module (#1331) * Pubstack Analytics V1 (#11) * V1 Pubstack (#7) * feat: Add Pubstack Logger (#6) * first version of pubstack analytics * bypass viperconfig * commit #1 * gofmt * update configuration and make the tests pass * add readme on how to configure the adapter and update the network calls * update logging and fix intake url definition * feat: Pubstack Analytics Connector * fixing go mod * fix: bad behaviour on appending path to auction url * add buffering * support bootstyrap like configuration * implement route for all the objects * supports termination signal handling for goroutines * move readme to the correct location * wording * enable configuration reload + add tests * fix logs messages * fix tests * fix log line * conclude merge * merge * update go mod Co-authored-by: Amaury Ravanel * fix duplicated channel keys Co-authored-by: Amaury Ravanel * first pass - PR reviews * rename channel* -> eventChannel * dead code * Review (#10) * use json.Decoder * update documentation * use nil instead []byte("") * clean code * do not use http.DefaultClient * fix race condition (need validation) * separate the sender and buffer logics * refactor the default configuration * remove error counter * Review GP + AR * updating default config * add more logs * remove alias fields in json * fix json serializer * close event channels Co-authored-by: Amaury Ravanel * fix race condition * first pass (pr reviews) * refactor: store enabled modules into a dedicated struct * stop goroutine * test: improve coverage * PR Review * Revert "refactor: store enabled modules into a dedicated struct" This reverts commit f57d9d61680c74244effc39a5d96d6cbb2f19f7d. # Conflicts: # analytics/config/config_test.go Co-authored-by: Amaury Ravanel --- analytics/clients/http.go | 12 + analytics/config/config.go | 17 ++ analytics/config/config_test.go | 41 +++ analytics/pubstack/README.md | 28 ++ analytics/pubstack/config.go | 51 ++++ analytics/pubstack/config_test.go | 102 +++++++ .../pubstack/eventchannel/eventchannel.go | 137 +++++++++ .../eventchannel/eventchannel_test.go | 136 +++++++++ analytics/pubstack/eventchannel/sender.go | 45 +++ .../pubstack/eventchannel/sender_test.go | 40 +++ analytics/pubstack/helpers/json.go | 88 ++++++ analytics/pubstack/helpers/json_test.go | 61 ++++ .../pubstack/mocks/mock_openrtb_request.json | 64 ++++ .../pubstack/mocks/mock_openrtb_response.json | 91 ++++++ analytics/pubstack/pubstack_module.go | 273 ++++++++++++++++++ analytics/pubstack/pubstack_module_test.go | 186 ++++++++++++ config/config.go | 24 +- go.mod | 1 + go.sum | 2 + 19 files changed, 1398 insertions(+), 1 deletion(-) create mode 100644 analytics/clients/http.go create mode 100644 analytics/pubstack/README.md create mode 100644 analytics/pubstack/config.go create mode 100644 analytics/pubstack/config_test.go create mode 100644 analytics/pubstack/eventchannel/eventchannel.go create mode 100644 analytics/pubstack/eventchannel/eventchannel_test.go create mode 100644 analytics/pubstack/eventchannel/sender.go create mode 100644 analytics/pubstack/eventchannel/sender_test.go create mode 100644 analytics/pubstack/helpers/json.go create mode 100644 analytics/pubstack/helpers/json_test.go create mode 100644 analytics/pubstack/mocks/mock_openrtb_request.json create mode 100644 analytics/pubstack/mocks/mock_openrtb_response.json create mode 100644 analytics/pubstack/pubstack_module.go create mode 100644 analytics/pubstack/pubstack_module_test.go diff --git a/analytics/clients/http.go b/analytics/clients/http.go new file mode 100644 index 00000000000..bc7f863ebdd --- /dev/null +++ b/analytics/clients/http.go @@ -0,0 +1,12 @@ +package clients + +import ( + "net/http" +) + +var defaultHttpInstance = http.DefaultClient + +func GetDefaultHttpInstance() *http.Client { + // TODO 2020-06-22 @see https://github.com/prebid/prebid-server/pull/1331#discussion_r436110097 + return defaultHttpInstance +} diff --git a/analytics/config/config.go b/analytics/config/config.go index 7be7c8ecca3..7f7ded0ffc4 100644 --- a/analytics/config/config.go +++ b/analytics/config/config.go @@ -3,7 +3,9 @@ package config import ( "github.com/golang/glog" "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/analytics/clients" "github.com/prebid/prebid-server/analytics/filesystem" + "github.com/prebid/prebid-server/analytics/pubstack" "github.com/prebid/prebid-server/config" ) @@ -17,6 +19,21 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { glog.Fatalf("Could not initialize FileLogger for file %v :%v", analytics.File.Filename, err) } } + if analytics.Pubstack.Enabled { + pubstackModule, err := pubstack.NewPubstackModule( + clients.GetDefaultHttpInstance(), + analytics.Pubstack.ScopeId, + analytics.Pubstack.IntakeUrl, + analytics.Pubstack.ConfRefresh, + analytics.Pubstack.Buffers.EventCount, + analytics.Pubstack.Buffers.BufferSize, + analytics.Pubstack.Buffers.Timeout) + if err == nil { + modules = append(modules, pubstackModule) + } else { + glog.Errorf("Could not initialize PubstackModule: %v", err) + } + } return modules } diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 7d97fb5f1be..583d475e786 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -1,6 +1,7 @@ package config import ( + "github.com/stretchr/testify/assert" "net/http" "os" "testing" @@ -73,6 +74,13 @@ func initAnalytics(count *int) analytics.PBSAnalyticsModule { } func TestNewPBSAnalytics(t *testing.T) { + pbsAnalytics := NewPBSAnalytics(&config.Analytics{}) + instance := pbsAnalytics.(enabledAnalytics) + + assert.Equal(t, len(instance), 0) +} + +func TestNewPBSAnalytics_FileLogger(t *testing.T) { if _, err := os.Stat(TEST_DIR); os.IsNotExist(err) { if err = os.MkdirAll(TEST_DIR, 0755); err != nil { t.Fatalf("Could not create test directory for FileLogger") @@ -88,4 +96,37 @@ func TestNewPBSAnalytics(t *testing.T) { default: t.Fatalf("Failed to initialize analytics module") } + + pbsAnalytics := NewPBSAnalytics(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) + instance := pbsAnalytics.(enabledAnalytics) + + assert.Equal(t, len(instance), 1) +} + +func TestNewPBSAnalytics_Pubstack(t *testing.T) { + + pbsAnalyticsWithoutError := NewPBSAnalytics(&config.Analytics{ + Pubstack: config.Pubstack{ + Enabled: true, + ScopeId: "scopeId", + IntakeUrl: "https://pubstack.io/intake", + Buffers: config.PubstackBuffer{ + BufferSize: "100KB", + EventCount: 0, + Timeout: "30s", + }, + ConfRefresh: "2h", + }, + }) + instanceWithoutError := pbsAnalyticsWithoutError.(enabledAnalytics) + + assert.Equal(t, len(instanceWithoutError), 1) + + pbsAnalyticsWithError := NewPBSAnalytics(&config.Analytics{ + Pubstack: config.Pubstack{ + Enabled: true, + }, + }) + instanceWithError := pbsAnalyticsWithError.(enabledAnalytics) + assert.Equal(t, len(instanceWithError), 0) } diff --git a/analytics/pubstack/README.md b/analytics/pubstack/README.md new file mode 100644 index 00000000000..51c5fdb6bb3 --- /dev/null +++ b/analytics/pubstack/README.md @@ -0,0 +1,28 @@ +# Pubstack Analytics + +In order to use the pubstack analytics module, it needs to be configured by the host. + +You can configure the server using the following environment variables: + +```bash +export PBS_ANALYTICS_PUBSTACK_ENABLED="true" +export PBS_ANALYTICS_PUBSTACK_ENDPOINT="https://openrtb.preview.pubstack.io/v1/openrtb2" +export PBS_ANALYTICS_PUBSTACK_SCOPEID= # should be an UUIDv4 +``` + +Or using the pbs configuration file and by appending the following block: + +```yaml +analytics: + pubstack: + # Required properties + enabled: true + endpoint: "https://openrtb.preview.pubstack.io/v1/openrtb2" + scopeid: "" # The scopeId provided by the Pubstack Support Team + # Optional properties (advanced configuration) + configuration_refresh_delay: "2h" # Dynamic configuration delay + buffers: # Flush events to Pubstack when (first condition reached) + size: "2MB" # greater than 2MB + count : 100 # greater than 100 events + timeout: "15m" # greater than 15 minutes +``` \ No newline at end of file diff --git a/analytics/pubstack/config.go b/analytics/pubstack/config.go new file mode 100644 index 00000000000..472acf68ead --- /dev/null +++ b/analytics/pubstack/config.go @@ -0,0 +1,51 @@ +package pubstack + +import ( + "encoding/json" + "github.com/docker/go-units" + "net/http" + "net/url" + "time" +) + +func fetchConfig(client *http.Client, endpoint *url.URL) (*Configuration, error) { + + res, err := client.Get(endpoint.String()) + if err != nil { + return nil, err + } + + defer res.Body.Close() + c := Configuration{} + err = json.NewDecoder(res.Body).Decode(&c) + if err != nil { + return nil, err + } + return &c, nil +} + +func newBufferConfig(count int, size, duration string) (*bufferConfig, error) { + pDuration, err := time.ParseDuration(duration) + if err != nil { + return nil, err + } + pSize, err := units.FromHumanSize(size) + if err != nil { + return nil, err + } + return &bufferConfig{ + pDuration, + int64(count), + pSize, + }, nil +} + +func (a *Configuration) isSameAs(b *Configuration) bool { + sameEndpoint := a.Endpoint == b.Endpoint + sameScopeID := a.ScopeID == b.ScopeID + sameFeature := len(a.Features) == len(b.Features) + for key := range a.Features { + sameFeature = sameFeature && a.Features[key] == b.Features[key] + } + return sameFeature && sameEndpoint && sameScopeID +} diff --git a/analytics/pubstack/config_test.go b/analytics/pubstack/config_test.go new file mode 100644 index 00000000000..bb6fd0bddbb --- /dev/null +++ b/analytics/pubstack/config_test.go @@ -0,0 +1,102 @@ +package pubstack + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +func TestFetchConfig(t *testing.T) { + configResponse := `{ + "scopeId": "scopeId", + "endpoint": "https://pubstack.io", + "features": { + "auction": true, + "cookiesync": true, + "amp": true, + "setuid": false, + "video": false + } + }` + + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + res.Write([]byte(configResponse)) + res.WriteHeader(200) + })) + + defer server.Close() + + endpoint, _ := url.Parse(server.URL) + cfg, _ := fetchConfig(server.Client(), endpoint) + + assert.Equal(t, cfg.ScopeID, "scopeId") + assert.Equal(t, cfg.Endpoint, "https://pubstack.io") + assert.Equal(t, cfg.Features[auction], true) + assert.Equal(t, cfg.Features[cookieSync], true) + assert.Equal(t, cfg.Features[amp], true) + assert.Equal(t, cfg.Features[setUID], false) + assert.Equal(t, cfg.Features[video], false) +} + +func TestFetchConfig_Error(t *testing.T) { + configResponse := `{ + "error": "scopeId", + }` + + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + res.Write([]byte(configResponse)) + res.WriteHeader(200) + })) + + defer server.Close() + + endpoint, _ := url.Parse(server.URL) + cfg, err := fetchConfig(server.Client(), endpoint) + + assert.Nil(t, cfg) + assert.NotNil(t, err) +} + +func TestIsSameAs(t *testing.T) { + copyConfig := func(conf Configuration) *Configuration { + newConfig := conf + newConfig.Features = make(map[string]bool) + for k := range conf.Features { + newConfig.Features[k] = conf.Features[k] + } + return &newConfig + } + + a := &Configuration{ + ScopeID: "scopeId", + Endpoint: "endpoint", + Features: map[string]bool{ + "auction": true, + "cookiesync": true, + "amp": true, + "setuid": false, + "video": false, + }, + } + + assert.True(t, a.isSameAs(copyConfig(*a))) + + b := copyConfig(*a) + b.ScopeID = "anotherId" + assert.False(t, a.isSameAs(b)) + + b = copyConfig(*a) + b.Endpoint = "anotherEndpoint" + assert.False(t, a.isSameAs(b)) + + b = copyConfig(*a) + b.Features["auction"] = true + assert.True(t, a.isSameAs(b)) + b.Features["auction"] = false + assert.False(t, a.isSameAs(b)) + +} diff --git a/analytics/pubstack/eventchannel/eventchannel.go b/analytics/pubstack/eventchannel/eventchannel.go new file mode 100644 index 00000000000..b8dc4dd8e28 --- /dev/null +++ b/analytics/pubstack/eventchannel/eventchannel.go @@ -0,0 +1,137 @@ +package eventchannel + +import ( + "bytes" + "compress/gzip" + "sync" + "time" + + "github.com/golang/glog" +) + +type Metrics struct { + bufferSize int64 + eventCount int64 +} +type Limit struct { + maxByteSize int64 + maxEventCount int64 + maxTime time.Duration +} +type EventChannel struct { + gz *gzip.Writer + buff *bytes.Buffer + + ch chan []byte + endCh chan int + metrics Metrics + muxGzBuffer sync.RWMutex + send Sender + limit Limit +} + +func NewEventChannel(sender Sender, maxByteSize, maxEventCount int64, maxTime time.Duration) *EventChannel { + b := &bytes.Buffer{} + gzw := gzip.NewWriter(b) + + c := EventChannel{ + gz: gzw, + buff: b, + ch: make(chan []byte), + endCh: make(chan int), + metrics: Metrics{}, + send: sender, + limit: Limit{maxByteSize, maxEventCount, maxTime}, + } + go c.start() + return &c +} + +func (c *EventChannel) Push(event []byte) { + c.ch <- event +} + +func (c *EventChannel) Close() { + c.endCh <- 1 +} + +func (c *EventChannel) buffer(event []byte) { + c.muxGzBuffer.Lock() + defer c.muxGzBuffer.Unlock() + + _, err := c.gz.Write(event) + if err != nil { + glog.Warning("[pubstack] fail to compress, skip the event") + return + } + + c.metrics.eventCount++ + c.metrics.bufferSize += int64(len(event)) +} + +func (c *EventChannel) isBufferFull() bool { + c.muxGzBuffer.RLock() + defer c.muxGzBuffer.RUnlock() + return c.metrics.eventCount >= c.limit.maxEventCount || c.metrics.bufferSize >= c.limit.maxByteSize +} + +func (c *EventChannel) reset() { + // reset buffer + c.gz.Reset(c.buff) + c.buff.Reset() + + // reset metrics + c.metrics.eventCount = 0 + c.metrics.bufferSize = 0 +} + +func (c *EventChannel) flush() { + c.muxGzBuffer.Lock() + defer c.muxGzBuffer.Unlock() + + if c.metrics.eventCount == 0 || c.metrics.bufferSize == 0 { + return + } + + // finish writing gzip header + err := c.gz.Flush() + if err != nil { + glog.Warning("[pubstack] fail to flush gzipped buffer") + return + } + + // copy the current buffer to send the payload in a new thread + payload := make([]byte, c.buff.Len()) + _, err = c.buff.Read(payload) + if err != nil { + glog.Warning("[pubstack] fail to copy the buffer") + return + } + + // reset buffers and writers + c.reset() + + // send events (async) + go c.send(payload) +} + +func (c *EventChannel) start() { + ticker := time.NewTicker(c.limit.maxTime) + + for { + select { + case <-c.endCh: + c.flush() + return + // event is received + case event := <-c.ch: + c.buffer(event) + if c.isBufferFull() { + c.flush() + } + // time between 2 flushes has passed + case <-ticker.C: + c.flush() + } + } +} diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go new file mode 100644 index 00000000000..9fdcfe976a6 --- /dev/null +++ b/analytics/pubstack/eventchannel/eventchannel_test.go @@ -0,0 +1,136 @@ +package eventchannel + +import ( + "bytes" + "compress/gzip" + "github.com/stretchr/testify/assert" + "io/ioutil" + "sync" + "testing" + "time" +) + +var maxByteSize = int64(15) +var maxEventCount = int64(3) +var maxTime = 2 * time.Hour + +func readGz(encoded bytes.Buffer) string { + gr, _ := gzip.NewReader(bytes.NewBuffer(encoded.Bytes())) + defer gr.Close() + + decoded, _ := ioutil.ReadAll(gr) + return string(decoded) +} + +func newSender(data *[]byte) Sender { + mux := &sync.Mutex{} + return func(payload []byte) error { + mux.Lock() + defer mux.Unlock() + event := bytes.Buffer{} + event.Write(payload) + *data = append(*data, readGz(event)...) + return nil + } +} + +func TestEventChannel_isBufferFull(t *testing.T) { + + send := func(_ []byte) error { return nil } + + eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + defer eventChannel.Close() + + eventChannel.buffer([]byte("one")) + eventChannel.buffer([]byte("two")) + + assert.Equal(t, eventChannel.isBufferFull(), false) + + eventChannel.buffer([]byte("three")) + + assert.Equal(t, eventChannel.isBufferFull(), true) + + eventChannel.reset() + + assert.Equal(t, eventChannel.isBufferFull(), false) + + eventChannel.buffer([]byte("big-event-abcdefghijklmnopqrstuvwxyz")) + + assert.Equal(t, eventChannel.isBufferFull(), true) + +} + +func TestEventChannel_reset(t *testing.T) { + send := func(_ []byte) error { return nil } + + eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + defer eventChannel.Close() + + assert.Equal(t, eventChannel.metrics.eventCount, int64(0)) + assert.Equal(t, eventChannel.metrics.bufferSize, int64(0)) + + eventChannel.buffer([]byte("one")) + eventChannel.buffer([]byte("two")) + + assert.NotEqual(t, eventChannel.metrics.eventCount, int64(0)) + assert.NotEqual(t, eventChannel.metrics.bufferSize, int64(0)) + + eventChannel.reset() + + assert.Equal(t, eventChannel.buff.Len(), 0) + assert.Equal(t, eventChannel.metrics.eventCount, int64(0)) + assert.Equal(t, eventChannel.metrics.bufferSize, int64(0)) +} + +func TestEventChannel_flush(t *testing.T) { + data := make([]byte, 0) + send := newSender(&data) + + eventChannel := NewEventChannel(send, maxByteSize, maxEventCount, maxTime) + defer eventChannel.Close() + + eventChannel.buffer([]byte("one")) + eventChannel.buffer([]byte("two")) + eventChannel.buffer([]byte("three")) + eventChannel.flush() + time.Sleep(10 * time.Millisecond) + + assert.Equal(t, string(data), "onetwothree") +} + +func TestEventChannel_close(t *testing.T) { + data := make([]byte, 0) + send := newSender(&data) + + eventChannel := NewEventChannel(send, 15000, 15000, 2*time.Hour) + + eventChannel.buffer([]byte("one")) + eventChannel.buffer([]byte("two")) + eventChannel.buffer([]byte("three")) + eventChannel.Close() + + time.Sleep(10 * time.Millisecond) + + assert.Equal(t, string(data), "onetwothree") +} + +func TestEventChannel_Push(t *testing.T) { + data := make([]byte, 0) + send := newSender(&data) + + eventChannel := NewEventChannel(send, 15000, 5, 5*time.Millisecond) + defer eventChannel.Close() + + eventChannel.Push([]byte("one")) + eventChannel.Push([]byte("two")) + eventChannel.Push([]byte("three")) + eventChannel.Push([]byte("four")) + eventChannel.Push([]byte("five")) + eventChannel.Push([]byte("six")) + eventChannel.Push([]byte("seven")) + + time.Sleep(10 * time.Millisecond) + + assert.Equal(t, string(data), "onetwothreefourfivesixseven") + +} diff --git a/analytics/pubstack/eventchannel/sender.go b/analytics/pubstack/eventchannel/sender.go new file mode 100644 index 00000000000..951de4d414e --- /dev/null +++ b/analytics/pubstack/eventchannel/sender.go @@ -0,0 +1,45 @@ +package eventchannel + +import ( + "bytes" + "fmt" + "github.com/golang/glog" + "net/http" + "net/url" + "path" +) + +type Sender = func(payload []byte) error + +func NewHttpSender(client *http.Client, endpoint string) Sender { + return func(payload []byte) error { + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(payload)) + if err != nil { + glog.Error(err) + return err + } + + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Encoding", "gzip") + + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + glog.Errorf("[pubstack] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) + return fmt.Errorf("wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) + } + return nil + } +} + +func BuildEndpointSender(client *http.Client, baseUrl string, module string) Sender { + endpoint, err := url.Parse(baseUrl) + if err != nil { + glog.Error(err) + } + endpoint.Path = path.Join(endpoint.Path, "intake", module) + return NewHttpSender(client, endpoint.String()) +} diff --git a/analytics/pubstack/eventchannel/sender_test.go b/analytics/pubstack/eventchannel/sender_test.go new file mode 100644 index 00000000000..1185435e4ab --- /dev/null +++ b/analytics/pubstack/eventchannel/sender_test.go @@ -0,0 +1,40 @@ +package eventchannel + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildEndpointSender(t *testing.T) { + requestBody := make([]byte, 10) + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + requestBody, _ = ioutil.ReadAll(req.Body) + res.WriteHeader(200) + })) + + defer server.Close() + + sender := BuildEndpointSender(server.Client(), server.URL, "module") + err := sender([]byte("message")) + + assert.Equal(t, requestBody, []byte("message")) + assert.Nil(t, err) +} + +func TestBuildEndpointSender_Error(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(400) + })) + + defer server.Close() + + sender := BuildEndpointSender(server.Client(), server.URL, "module") + err := sender([]byte("message")) + + assert.NotNil(t, err) +} diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go new file mode 100644 index 00000000000..f02f1120626 --- /dev/null +++ b/analytics/pubstack/helpers/json.go @@ -0,0 +1,88 @@ +package helpers + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/prebid-server/analytics" +) + +func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, error) { + b, err := json.Marshal(&struct { + Scope string `json:"scope"` + *analytics.AuctionObject + }{ + Scope: scope, + AuctionObject: ao, + }) + + if err == nil { + b = append(b, byte('\n')) + return b, nil + } + return nil, fmt.Errorf("auction object badly formed %v", err) +} + +func JsonifyVideoObject(vo *analytics.VideoObject, scope string) ([]byte, error) { + b, err := json.Marshal(&struct { + Scope string `json:"scope"` + *analytics.VideoObject + }{ + Scope: scope, + VideoObject: vo, + }) + + if err == nil { + b = append(b, byte('\n')) + return b, nil + } + return nil, fmt.Errorf("video object badly formed %v", err) +} + +func JsonifyCookieSync(cso *analytics.CookieSyncObject, scope string) ([]byte, error) { + b, err := json.Marshal(&struct { + Scope string `json:"scope"` + *analytics.CookieSyncObject + }{ + Scope: scope, + CookieSyncObject: cso, + }) + + if err == nil { + b = append(b, byte('\n')) + return b, nil + } + return nil, fmt.Errorf("cookie sync object badly formed %v", err) +} + +func JsonifySetUIDObject(so *analytics.SetUIDObject, scope string) ([]byte, error) { + b, err := json.Marshal(&struct { + Scope string `json:"scope"` + *analytics.SetUIDObject + }{ + Scope: scope, + SetUIDObject: so, + }) + + if err == nil { + b = append(b, byte('\n')) + return b, nil + } + return nil, fmt.Errorf("set UID object badly formed %v", err) +} + +func JsonifyAmpObject(ao *analytics.AmpObject, scope string) ([]byte, error) { + b, err := json.Marshal(&struct { + Scope string `json:"scope"` + *analytics.AmpObject + }{ + Scope: scope, + AmpObject: ao, + }) + + if err == nil { + b = append(b, byte('\n')) + return b, nil + } + return nil, fmt.Errorf("amp object badly formed %v", err) +} diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go new file mode 100644 index 00000000000..4e36e8db2be --- /dev/null +++ b/analytics/pubstack/helpers/json_test.go @@ -0,0 +1,61 @@ +package helpers + +import ( + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/usersync" + "net/http" + "testing" +) + +func TestJsonifyAuctionObject(t *testing.T) { + ao := &analytics.AuctionObject{ + Status: http.StatusOK, + } + if _, err := JsonifyAuctionObject(ao, "scopeId"); err != nil { + t.Fail() + } + +} + +func TestJsonifyVideoObject(t *testing.T) { + vo := &analytics.VideoObject{ + Status: http.StatusOK, + } + if _, err := JsonifyVideoObject(vo, "scopeId"); err != nil { + t.Fail() + } +} + +func TestJsonifyCookieSync(t *testing.T) { + cso := &analytics.CookieSyncObject{ + Status: http.StatusOK, + BidderStatus: []*usersync.CookieSyncBidders{}, + } + if _, err := JsonifyCookieSync(cso, "scopeId"); err != nil { + t.Fail() + } +} + +func TestJsonifySetUIDObject(t *testing.T) { + so := &analytics.SetUIDObject{ + Status: http.StatusOK, + Bidder: "any-bidder", + UID: "uid string", + } + if _, err := JsonifySetUIDObject(so, "scopeId"); err != nil { + t.Fail() + } +} + +func TestJsonifyAmpObject(t *testing.T) { + ao := &analytics.AmpObject{ + Status: http.StatusOK, + Errors: make([]error, 0), + AuctionResponse: &openrtb.BidResponse{}, + AmpTargetingValues: map[string]string{}, + } + if _, err := JsonifyAmpObject(ao, "scopeId"); err != nil { + t.Fail() + } +} diff --git a/analytics/pubstack/mocks/mock_openrtb_request.json b/analytics/pubstack/mocks/mock_openrtb_request.json new file mode 100644 index 00000000000..03b9665b247 --- /dev/null +++ b/analytics/pubstack/mocks/mock_openrtb_request.json @@ -0,0 +1,64 @@ +{ + "id": "19c2eeb8-824c-4604-af41-a59b2b7bb895", + "site": { + "page": "https%3A%2F%2Fdebug.mediasquare.fr%2Fdebug%2Fprebid%2Fmsq_desktop.html%3Fpbjs_debug%3Dtrue" + }, + "user": { + "ext": {} + }, + "regs": { + "ext": {} + }, + "test": 1, + "imp": [ + { + "id": "0341252e-b3b0-4dff-a0ef-1ced63369bd5", + "ext": { + "appnexus": { + "placementId": 5724999 + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 970, + "h": 250 + } + ] + } + }, + { + "id": "3ac0ffa3-01de-44d2-9baf-1fee79026624", + "ext": { + "msqClassic": { + "placementId": 10471298 + } + }, + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustmentfactors": { + "msqClassic": 0.8, + "msqBrand": 0.8, + "msqMax": 0.8, + "msqMaxView": 0.8 + } + } + } +} \ No newline at end of file diff --git a/analytics/pubstack/mocks/mock_openrtb_response.json b/analytics/pubstack/mocks/mock_openrtb_response.json new file mode 100644 index 00000000000..6f4d1965b8c --- /dev/null +++ b/analytics/pubstack/mocks/mock_openrtb_response.json @@ -0,0 +1,91 @@ +{ + "id": "19c2eeb8-824c-4604-af41-a59b2b7bb895", + "seatbid": [{ + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "0341252e-b3b0-4dff-a0ef-1ced63369bd5", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "h": 970, + "w": 250, + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000 + } + } + }, + { + "id": "7706636740145184842", + "impid": "0341252e-b3b0-4dff-a0ef-1ced63369bd5", + "price": 0.0, + "adid": "29681114", + "adm": "some-test-ad2", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681114", + "cid": "959", + "crid": "29681114", + "h": 970, + "w": 250, + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000 + } + } + },{ + "id": "7706636740145184842", + "impid": "3ac0ffa3-01de-44d2-9baf-1fee79026624", + "price": 0.5234, + "adid": "29681113", + "adm": "some-test-ad2", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681113", + "cid": "959", + "crid": "29681113", + "h": 970, + "w": 250, + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000 + } + } + }] + }, { + "seat": "improvedigital", + "bid": [{ + "id": "randomid", + "impid": "0341252e-b3b0-4dff-a0ef-1ced63369bd5", + "price": 0.510000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" +} diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go new file mode 100644 index 00000000000..9f1a81c7232 --- /dev/null +++ b/analytics/pubstack/pubstack_module.go @@ -0,0 +1,273 @@ +package pubstack + +import ( + "fmt" + "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" + "net/http" + "net/url" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/analytics/pubstack/helpers" + + "github.com/prebid/prebid-server/analytics" +) + +type Configuration struct { + ScopeID string `json:"scopeId"` + Endpoint string `json:"endpoint"` + Features map[string]bool `json:"features"` +} + +// routes for events +const ( + auction = "auction" + cookieSync = "cookiesync" + amp = "amp" + setUID = "setuid" + video = "video" +) + +type bufferConfig struct { + timeout time.Duration + count int64 + size int64 +} + +type PubstackModule struct { + eventChannels map[string]*eventchannel.EventChannel + httpClient *http.Client + configCh chan *Configuration + sigTermCh chan os.Signal + scope string + cfg *Configuration + buffsCfg *bufferConfig + muxConfig sync.RWMutex +} + +func NewPubstackModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string) (analytics.PBSAnalyticsModule, error) { + glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) + + // parse args + + refreshDelay, err := time.ParseDuration(configRefreshDelay) + if err != nil { + return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.configuration_refresh_delay, :%v", err) + } + + bufferCfg, err := newBufferConfig(maxEventCount, maxByteSize, maxTime) + if err != nil { + return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.buffers, :%v", err) + } + + defaultFeatures := map[string]bool{ + auction: false, + video: false, + amp: false, + cookieSync: false, + setUID: false, + } + + defaultConfig := &Configuration{ + ScopeID: scope, + Endpoint: endpoint, + Features: defaultFeatures, + } + + pb := PubstackModule{ + scope: scope, + httpClient: client, + cfg: defaultConfig, + buffsCfg: bufferCfg, + sigTermCh: make(chan os.Signal), + configCh: make(chan *Configuration), + eventChannels: make(map[string]*eventchannel.EventChannel), + muxConfig: sync.RWMutex{}, + } + signal.Notify(pb.sigTermCh, os.Interrupt, syscall.SIGTERM) + + configUrl, err := url.Parse(pb.cfg.Endpoint + "/bootstrap?scopeId=" + pb.cfg.ScopeID) + if err != nil { + glog.Error(err) + return nil, err + } + go pb.start(configUrl, refreshDelay) + go func() { + err = pb.reloadConfig(configUrl) + if err != nil { + glog.Errorf("[pubstack] Fail to fetch remote configuration: %v", err) + } + }() + + glog.Info("[pubstack] Pubstack analytics configured and ready") + return &pb, nil +} + +func (p *PubstackModule) LogAuctionObject(ao *analytics.AuctionObject) { + p.muxConfig.RLock() + defer p.muxConfig.RUnlock() + + if !p.isFeatureEnable(auction) { + return + } + + // serialize event + payload, err := helpers.JsonifyAuctionObject(ao, p.scope) + if err != nil { + glog.Warning("[pubstack] Cannot serialize auction") + return + } + + p.eventChannels[auction].Push(payload) +} + +func (p *PubstackModule) LogVideoObject(vo *analytics.VideoObject) { + p.muxConfig.RLock() + defer p.muxConfig.RUnlock() + + if !p.isFeatureEnable(video) { + return + } + + // serialize event + payload, err := helpers.JsonifyVideoObject(vo, p.scope) + if err != nil { + glog.Warning("[pubstack] Cannot serialize video") + return + } + + p.eventChannels[video].Push(payload) +} + +func (p *PubstackModule) LogSetUIDObject(so *analytics.SetUIDObject) { + p.muxConfig.RLock() + defer p.muxConfig.RUnlock() + + if !p.isFeatureEnable(setUID) { + return + } + + // serialize event + payload, err := helpers.JsonifySetUIDObject(so, p.scope) + if err != nil { + glog.Warning("[pubstack] Cannot serialize video") + return + } + + p.eventChannels[setUID].Push(payload) +} + +func (p *PubstackModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { + p.muxConfig.RLock() + defer p.muxConfig.RUnlock() + + if !p.isFeatureEnable(cookieSync) { + return + } + + // serialize event + payload, err := helpers.JsonifyCookieSync(cso, p.scope) + if err != nil { + glog.Warning("[pubstack] Cannot serialize video") + return + } + + p.eventChannels[cookieSync].Push(payload) + +} + +func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) { + p.muxConfig.RLock() + defer p.muxConfig.RUnlock() + + if !p.isFeatureEnable(amp) { + return + } + + // serialize event + payload, err := helpers.JsonifyAmpObject(ao, p.scope) + if err != nil { + glog.Warning("[pubstack] Cannot serialize video") + return + } + + p.eventChannels[amp].Push(payload) + +} + +func (p *PubstackModule) reloadConfig(configUrl *url.URL) error { + config, err := fetchConfig(p.httpClient, configUrl) + if err != nil { + return err + } + p.configCh <- config + return nil +} + +func (p *PubstackModule) start(configUrl *url.URL, refreshDelay time.Duration) { + + tick := time.NewTicker(refreshDelay) + + for { + select { + case <-p.sigTermCh: + p.closeAllEventChannels() + return + case config := <-p.configCh: + p.updateConfig(config) + glog.Infof("[pubstack] Updating config: %v", p.cfg) + case <-tick.C: + go func() { + err := p.reloadConfig(configUrl) + if err != nil { + glog.Errorf("[pubstack] Fail to fetch remote configuration: %v", err) + } + }() + } + } + +} + +func (p *PubstackModule) updateConfig(config *Configuration) { + p.muxConfig.Lock() + defer p.muxConfig.Unlock() + + if p.cfg.isSameAs(config) { + return + } + + p.cfg = config + p.closeAllEventChannels() + + if p.isFeatureEnable(amp) { + p.eventChannels[amp] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, amp), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + } + if p.isFeatureEnable(auction) { + p.eventChannels[auction] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, auction), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + } + if p.isFeatureEnable(cookieSync) { + p.eventChannels[cookieSync] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, cookieSync), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + } + if p.isFeatureEnable(video) { + p.eventChannels[video] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, video), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + } + if p.isFeatureEnable(setUID) { + p.eventChannels[setUID] = eventchannel.NewEventChannel(eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, setUID), p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) + } +} + +func (p *PubstackModule) closeAllEventChannels() { + for key, ch := range p.eventChannels { + ch.Close() + delete(p.eventChannels, key) + } +} + +func (p *PubstackModule) isFeatureEnable(feature string) bool { + val, ok := p.cfg.Features[feature] + return ok && val +} diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go new file mode 100644 index 00000000000..8d4dfdd689f --- /dev/null +++ b/analytics/pubstack/pubstack_module_test.go @@ -0,0 +1,186 @@ +package pubstack + +import ( + "encoding/json" + "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + "time" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/analytics" + "github.com/stretchr/testify/assert" +) + +func loadJsonFromFile() (*analytics.AuctionObject, error) { + req, err := os.Open("mocks/mock_openrtb_request.json") + if err != nil { + return nil, err + } + defer req.Close() + + reqCtn := openrtb.BidRequest{} + reqPayload, err := ioutil.ReadAll(req) + if err != nil { + return nil, err + } + + err = json.Unmarshal(reqPayload, &reqCtn) + if err != nil { + return nil, err + } + + res, err := os.Open("mocks/mock_openrtb_response.json") + if err != nil { + return nil, err + } + defer res.Close() + + resCtn := openrtb.BidResponse{} + resPayload, err := ioutil.ReadAll(res) + if err != nil { + return nil, err + } + + err = json.Unmarshal(resPayload, &resCtn) + if err != nil { + return nil, err + } + + return &analytics.AuctionObject{ + Request: &reqCtn, + Response: &resCtn, + }, nil +} + +func TestPubstackModule(t *testing.T) { + + remoteConfig := &Configuration{} + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + data, _ := json.Marshal(remoteConfig) + res.Write(data) + })) + client := server.Client() + + defer server.Close() + + // Loading Issues + _, err := NewPubstackModule(client, "scope", server.URL, "1z", 100, "90MB", "15m") + assert.NotNil(t, err) // should raise an error since we can't parse args // configRefreshDelay + + _, err = NewPubstackModule(client, "scope", server.URL, "1h", 100, "90z", "15m") + assert.NotNil(t, err) // should raise an error since we can't parse args // maxByte + + _, err = NewPubstackModule(client, "scope", server.URL, "1h", 100, "90MB", "15z") + assert.NotNil(t, err) // should raise an error since we can't parse args // maxTime + + // Loading OK + module, err := NewPubstackModule(client, "scope", server.URL, "10ms", 100, "90MB", "15m") + assert.Nil(t, err) + + // Default Configuration + pubstack, ok := module.(*PubstackModule) + assert.Equal(t, ok, true) //PBSAnalyticsModule is also a PubstackModule + assert.Equal(t, len(pubstack.cfg.Features), 5) + assert.Equal(t, pubstack.cfg.Features[auction], false) + assert.Equal(t, pubstack.cfg.Features[video], false) + assert.Equal(t, pubstack.cfg.Features[amp], false) + assert.Equal(t, pubstack.cfg.Features[setUID], false) + assert.Equal(t, pubstack.cfg.Features[cookieSync], false) + + assert.Equal(t, len(pubstack.eventChannels), 0) + + // Process Auction Event + counter := 0 + send := func(_ []byte) error { + counter++ + return nil + } + mockedEvent, err := loadJsonFromFile() + if err != nil { + t.Fail() + } + + pubstack.eventChannels[auction] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[video] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[amp] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[setUID] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[cookieSync] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + + pubstack.LogAuctionObject(mockedEvent) + pubstack.LogAmpObject(&analytics.AmpObject{ + Status: http.StatusOK, + }) + pubstack.LogCookieSyncObject(&analytics.CookieSyncObject{ + Status: http.StatusOK, + }) + pubstack.LogVideoObject(&analytics.VideoObject{ + Status: http.StatusOK, + }) + pubstack.LogSetUIDObject(&analytics.SetUIDObject{ + Status: http.StatusOK, + }) + + pubstack.closeAllEventChannels() + time.Sleep(10 * time.Millisecond) // process channel + assert.Equal(t, counter, 0) + + // Hot-Reload config + newFeatures := make(map[string]bool) + newFeatures[auction] = true + newFeatures[video] = true + newFeatures[amp] = true + newFeatures[cookieSync] = true + newFeatures[setUID] = true + + remoteConfig = &Configuration{ + ScopeID: "new-scope", + Endpoint: "new-endpoint", + Features: newFeatures, + } + + endpoint, _ := url.Parse(server.URL) + pubstack.reloadConfig(endpoint) + + time.Sleep(2 * time.Millisecond) // process channel + assert.Equal(t, len(pubstack.cfg.Features), 5) + assert.Equal(t, pubstack.cfg.Features[auction], true) + assert.Equal(t, pubstack.cfg.Features[video], true) + assert.Equal(t, pubstack.cfg.Features[amp], true) + assert.Equal(t, pubstack.cfg.Features[setUID], true) + assert.Equal(t, pubstack.cfg.Features[cookieSync], true) + assert.Equal(t, pubstack.cfg.ScopeID, "new-scope") + assert.Equal(t, pubstack.cfg.Endpoint, "new-endpoint") + assert.Equal(t, len(pubstack.eventChannels), 5) + + counter = 0 + pubstack.eventChannels[auction] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[video] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[amp] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[setUID] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + pubstack.eventChannels[cookieSync] = eventchannel.NewEventChannel(send, 2000, 1, 10*time.Second) + + pubstack.LogAuctionObject(mockedEvent) + pubstack.LogAmpObject(&analytics.AmpObject{ + Status: http.StatusOK, + }) + pubstack.LogCookieSyncObject(&analytics.CookieSyncObject{ + Status: http.StatusOK, + }) + pubstack.LogVideoObject(&analytics.VideoObject{ + Status: http.StatusOK, + }) + pubstack.LogSetUIDObject(&analytics.SetUIDObject{ + Status: http.StatusOK, + }) + pubstack.closeAllEventChannels() + time.Sleep(10 * time.Millisecond) + + assert.Equal(t, counter, 5) + +} diff --git a/config/config.go b/config/config.go index 8545523d238..67689d1ab1a 100755 --- a/config/config.go +++ b/config/config.go @@ -209,7 +209,8 @@ type LMT struct { } type Analytics struct { - File FileLogs `mapstructure:"file"` + File FileLogs `mapstructure:"file"` + Pubstack Pubstack `mapstructure:"pubstack"` } type CurrencyConverter struct { @@ -230,6 +231,20 @@ type FileLogs struct { Filename string `mapstructure:"filename"` } +type Pubstack struct { + Enabled bool `mapstructure:"enabled"` + ScopeId string `mapstructure:"scopeid"` + IntakeUrl string `mapstructure:"endpoint"` + Buffers PubstackBuffer `mapstructure:"buffers"` + ConfRefresh string `mapstructure:"configuration_refresh_delay"` +} + +type PubstackBuffer struct { + BufferSize string `mapstructure:"size"` + EventCount int `mapstructure:"count"` + Timeout string `mapstructure:"timeout"` +} + type HostCookie struct { Domain string `mapstructure:"domain"` Family string `mapstructure:"family"` @@ -855,6 +870,13 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("max_request_size", 1024*256) v.SetDefault("analytics.file.filename", "") + v.SetDefault("analytics.pubstack.endpoint", "https://s2s.pbstck.com/v1") + v.SetDefault("analytics.pubstack.scopeid", "change-me") + v.SetDefault("analytics.pubstack.enabled", false) + v.SetDefault("analytics.pubstack.configuration_refresh_delay", "2h") + v.SetDefault("analytics.pubstack.buffers.size", "2MB") + v.SetDefault("analytics.pubstack.buffers.count", 100) + v.SetDefault("analytics.pubstack.buffers.timeout", "900s") v.SetDefault("amp_timeout_adjustment_ms", 0) v.SetDefault("gdpr.host_vendor_id", 0) v.SetDefault("gdpr.usersync_if_ambiguous", false) diff --git a/go.mod b/go.mod index 00cadd31ce1..a5b5a161cf4 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 + github.com/docker/go-units v0.4.0 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible diff --git a/go.sum b/go.sum index 5eaf37cad9f..1ddab71332a 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsip 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= From 8740179c573f966147a91ce5f32b060bcc3e8243 Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 6 Aug 2020 17:12:08 +0200 Subject: [PATCH 156/603] New bid adapter for Smaato (#1413) Co-authored-by: vikram Co-authored-by: Stephan --- adapters/smaato/image.go | 53 ++++ adapters/smaato/image_test.go | 44 +++ adapters/smaato/params_test.go | 65 +++++ adapters/smaato/richmedia.go | 52 ++++ adapters/smaato/richmedia_test.go | 39 +++ adapters/smaato/smaato.go | 276 ++++++++++++++++++ adapters/smaato/smaato_test.go | 11 + .../exemplary/simple-banner-richMedia.json | 194 ++++++++++++ .../smaatotest/exemplary/simple-banner.json | 190 ++++++++++++ adapters/smaato/smaatotest/params/banner.json | 4 + .../supplemental/bad-adm-response.json | 166 +++++++++++ .../smaatotest/supplemental/bad-ext-req.json | 54 ++++ .../bad-imp-banner-format-req.json | 61 ++++ .../supplemental/bad-user-ext-data-req.json | 67 +++++ .../supplemental/bad-user-ext-req.json | 57 ++++ .../supplemental/no-consent-info.json | 137 +++++++++ .../smaatotest/supplemental/no-imp-req.json | 17 ++ config/config.go | 1 + docs/bidders/smaato.md | 42 +++ exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_smaato.go | 9 + static/bidder-info/smaato.yaml | 9 + static/bidder-params/smaato.json | 17 ++ usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1570 insertions(+) create mode 100644 adapters/smaato/image.go create mode 100644 adapters/smaato/image_test.go create mode 100644 adapters/smaato/params_test.go create mode 100644 adapters/smaato/richmedia.go create mode 100644 adapters/smaato/richmedia_test.go create mode 100644 adapters/smaato/smaato.go create mode 100644 adapters/smaato/smaato_test.go create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner.json create mode 100644 adapters/smaato/smaatotest/params/banner.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-adm-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-ext-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/no-consent-info.json create mode 100644 adapters/smaato/smaatotest/supplemental/no-imp-req.json create mode 100644 docs/bidders/smaato.md create mode 100644 openrtb_ext/imp_smaato.go create mode 100644 static/bidder-info/smaato.yaml create mode 100644 static/bidder-params/smaato.json diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go new file mode 100644 index 00000000000..582206ccb0c --- /dev/null +++ b/adapters/smaato/image.go @@ -0,0 +1,53 @@ +package smaato + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" +) + +type imageAd struct { + Image image `json:"image"` +} +type image struct { + Img img `json:"img"` + Impressiontrackers []string `json:"impressiontrackers"` + Clicktrackers []string `json:"clicktrackers"` +} +type img struct { + URL string `json:"url"` + W int `json:"w"` + H int `json:"h"` + Ctaurl string `json:"ctaurl"` +} + +func extractAdmImage(adapterResponseAdm string) (string, error) { + var imgMarkup string + var err error + + var imageAd imageAd + err = json.Unmarshal([]byte(adapterResponseAdm), &imageAd) + var image = imageAd.Image + + if err == nil { + var clickEvent strings.Builder + var impressionTracker strings.Builder + + for _, clicktracker := range image.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " + + "{cache: 'no-cache'});") + } + + for _, impression := range image.Impressiontrackers { + + impressionTracker.WriteString(fmt.Sprintf(``, impression)) + } + + imgMarkup = fmt.Sprintf(`
%s
`, + &clickEvent, url.QueryEscape(image.Img.Ctaurl), image. + Img.URL, image.Img.W, image.Img. + H, &impressionTracker) + } + return imgMarkup, err +} diff --git a/adapters/smaato/image_test.go b/adapters/smaato/image_test.go new file mode 100644 index 00000000000..5f39c857201 --- /dev/null +++ b/adapters/smaato/image_test.go @@ -0,0 +1,44 @@ +package smaato + +import ( + "testing" +) + +func TestRenderAdMarkup(t *testing.T) { + type args struct { + adType adMarkupType + adapterResponseAdm string + } + expectedResult := `
` + + `` + + `` + + `
` + + tests := []struct { + testName string + args args + result string + }{ + {"imageTest", args{"Img", + "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," + + "\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"}," + + "\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, + expectedResult, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) + if err != nil { + t.Errorf("error rendering ad markup: %v", err) + } + if got != tt.result { + t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + } + }) + } +} diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go new file mode 100644 index 00000000000..6c71cbe75c6 --- /dev/null +++ b/adapters/smaato/params_test.go @@ -0,0 +1,65 @@ +package smaato + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file intends to test static/bidder-params/smaato.json + +// These also validate the format of the external API: request.imp[i].bidRequestExt.smaato + +// TestValidParams makes sure that the Smaato schema accepts all imp.bidRequestExt fields which Smaato supports. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmaato, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected smaato params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the Smaato schema rejects all the imp.bidRequestExt fields which are not support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmaato, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"publisherId":"test-id-1234-smaato"}`, + `{"adspaceId": "1123581321"}`, + `{"publisherId":false}`, + `{"adspaceId":false}`, + `{"publisherId":0,"adspaceId": 1123581321}`, + `{"publisherId":false,"adspaceId": true}`, + `{"instl": 0}`, + `{"secure": 0}`, + `{"adspaceId": "1123581321","instl": 0,"secure": 0}`, + `{"instl": 0,"secure": 0}`, + `{"publisherId":"test-id-1234-smaato","instl": 0,"secure": 0}`, +} diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go new file mode 100644 index 00000000000..1c94a3555c1 --- /dev/null +++ b/adapters/smaato/richmedia.go @@ -0,0 +1,52 @@ +package smaato + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" +) + +type richMediaAd struct { + RichMedia richmedia `json:"richmedia"` +} +type mediadata struct { + Content string `json:"content"` + W int `json:"w"` + H int `json:"h"` +} + +type richmedia struct { + MediaData mediadata `json:"mediadata"` + Impressiontrackers []string `json:"impressiontrackers"` + Clicktrackers []string `json:"clicktrackers"` +} + +func extractAdmRichMedia(adapterResponseAdm string) (string, error) { + var richMediaMarkup string + var err error + + var richMediaAd richMediaAd + err = json.Unmarshal([]byte(adapterResponseAdm), &richMediaAd) + var richMedia = richMediaAd.RichMedia + + if err == nil { + var clickEvent strings.Builder + var impressionTracker strings.Builder + + for _, clicktracker := range richMedia.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " + + "{cache: 'no-cache'});") + } + for _, impression := range richMedia.Impressiontrackers { + + impressionTracker.WriteString(fmt.Sprintf(``, impression)) + } + + richMediaMarkup = fmt.Sprintf(`
%s%s
`, + &clickEvent, + richMedia.MediaData.Content, + &impressionTracker) + } + return richMediaMarkup, err +} diff --git a/adapters/smaato/richmedia_test.go b/adapters/smaato/richmedia_test.go new file mode 100644 index 00000000000..20fa1ba353c --- /dev/null +++ b/adapters/smaato/richmedia_test.go @@ -0,0 +1,39 @@ +package smaato + +import ( + "testing" +) + +func TestExtractAdmRichMedia(t *testing.T) { + type args struct { + adType adMarkupType + adapterResponseAdm string + } + expectedResult := `
hello
` + + `
` + tests := []struct { + testName string + args args + result string + }{ + {"richmediaTest", args{"Richmedia", "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\"," + + "" + "\"w\":350," + + "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, + expectedResult, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) + if err != nil { + t.Errorf("error rendering ad markup: %v", err) + } + if got != tt.result { + t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + } + }) + } +} diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go new file mode 100644 index 00000000000..06678d77a61 --- /dev/null +++ b/adapters/smaato/smaato.go @@ -0,0 +1,276 @@ +package smaato + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const clientVersion = "prebid_server_0.1" + +type adMarkupType string + +const ( + smtAdTypeImg adMarkupType = "Img" + smtAdTypeRichmedia adMarkupType = "Richmedia" +) + +// SmaatoAdapter describes a Smaato prebid server adapter. +type SmaatoAdapter struct { + URI string +} + +//userExt defines User.Ext object for Smaato +type userExt struct { + Data userExtData `json:"data"` +} + +type userExtData struct { + Keywords string `json:"keywords"` + Gender string `json:"gender"` + Yob int64 `json:"yob"` +} + +//userExt defines Site.Ext object for Smaato +type siteExt struct { + Data siteExtData `json:"data"` +} + +type siteExtData struct { + Keywords string `json:"keywords"` +} + +// NewSmaatoBidder creates a Smaato bid adapter. +func NewSmaatoBidder(uri string) *SmaatoAdapter { + return &SmaatoAdapter{ + URI: uri, + } +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + if len(request.Imp) == 0 { + errs = append(errs, &errortypes.BadInput{Message: "no impressions in bid request"}) + return nil, errs + } + + // Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId + publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") + if err != nil { + errs = append(errs, err) + return nil, errs + } + + for i := 0; i < len(request.Imp); i++ { + err := parseImpressionObject(&request.Imp[i]) + // If the parsing is failed, remove imp and add the error. + if err != nil { + errs = append(errs, err) + request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) + i-- + } + } + if request.Site != nil { + siteCopy := *request.Site + siteCopy.Publisher = &openrtb.Publisher{ID: publisherId} + + if request.Site.Ext != nil { + var siteExt siteExt + err := json.Unmarshal([]byte(request.Site.Ext), &siteExt) + if err != nil { + errs = append(errs, err) + return nil, errs + } + siteCopy.Keywords = siteExt.Data.Keywords + siteCopy.Ext = nil + } + request.Site = &siteCopy + } + + if request.User != nil && request.User.Ext != nil { + var userExt userExt + var userExtRaw map[string]json.RawMessage + + rawExtErr := json.Unmarshal(request.User.Ext, &userExtRaw) + if rawExtErr != nil { + errs = append(errs, rawExtErr) + return nil, errs + } + + userExtErr := json.Unmarshal([]byte(request.User.Ext), &userExt) + if userExtErr != nil { + errs = append(errs, userExtErr) + return nil, errs + } + + userCopy := *request.User + extractUserExtAttributes(userExt, &userCopy) + delete(userExtRaw, "data") + userCopy.Ext, err = json.Marshal(userExtRaw) + if err != nil { + errs = append(errs, err) + return nil, errs + } + request.User = &userCopy + } + + // Setting ext client info + type bidRequestExt struct { + Client string `json:"client"` + } + request.Ext, err = json.Marshal(bidRequestExt{Client: clientVersion}) + if err != nil { + errs = append(errs, err) + return nil, errs + } + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + uri := a.URI + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: uri, + Body: reqJSON, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{fmt.Errorf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + + var markupError error + bid.AdM, markupError = renderAdMarkup(getAdMarkupType(response, bid.AdM), bid.AdM) + if markupError != nil { + fmt.Println(markupError) + continue // no bid when broken ad markup + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + return bidResponse, nil +} + +func renderAdMarkup(adMarkupType adMarkupType, adMarkup string) (string, error) { + var markupError error + var adm string + switch adMarkupType { + case smtAdTypeImg: + adm, markupError = extractAdmImage(adMarkup) + case smtAdTypeRichmedia: + adm, markupError = extractAdmRichMedia(adMarkup) + default: + return "", fmt.Errorf("Unknown markup type %s", adMarkupType) + } + return adm, markupError +} + +func getAdMarkupType(response *adapters.ResponseData, adMarkup string) adMarkupType { + if admType := adMarkupType(response.Headers.Get("X-SMT-ADTYPE")); admType != "" { + return admType + } + if strings.HasPrefix(adMarkup, `{"image":`) { + return smtAdTypeImg + } + if strings.HasPrefix(adMarkup, `{"richmedia":`) { + return smtAdTypeRichmedia + } + return "" +} + +func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { + if banner.W != nil && banner.H != nil { + return banner, nil + } + if len(banner.Format) == 0 { + return banner, fmt.Errorf("No sizes provided for Banner %v", banner.Format) + } + bannerCopy := *banner + bannerCopy.W = new(uint64) + *bannerCopy.W = banner.Format[0].W + bannerCopy.H = new(uint64) + *bannerCopy.H = banner.Format[0].H + + return &bannerCopy, nil +} + +// parseImpressionObject parse the imp to get it ready to send to smaato +func parseImpressionObject(imp *openrtb.Imp) error { + adSpaceID, err := jsonparser.GetString(imp.Ext, "bidder", "adspaceId") + if err != nil { + return err + } + + // SMAATO supports banner impressions. + if imp.Banner != nil { + bannerCopy, err := assignBannerSize(imp.Banner) + if err != nil { + return err + } + imp.Banner = bannerCopy + imp.TagID = adSpaceID + imp.Ext = nil + return nil + } + return fmt.Errorf("invalid MediaType. SMAATO only supports Banner. Ignoring ImpID=%s", imp.ID) +} + +func extractUserExtAttributes(userExt userExt, userCopy *openrtb.User) { + gender := userExt.Data.Gender + if gender != "" { + userCopy.Gender = gender + } + + yob := userExt.Data.Yob + if yob != 0 { + userCopy.Yob = yob + } + + keywords := userExt.Data.Keywords + if keywords != "" { + userCopy.Keywords = keywords + } +} diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go new file mode 100644 index 00000000000..cf76d58de2c --- /dev/null +++ b/adapters/smaato/smaato_test.go @@ -0,0 +1,11 @@ +package smaato + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "smaatotest", NewSmaatoBidder("https://prebid/bidder")) +} diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json new file mode 100644 index 00000000000..7b662e8813a --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "gender": "M", + "keywords": "a,b", + "yob": 1984, + "ext": { + "consent": "gdprConsentString" + } + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
hello
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json new file mode 100644 index 00000000000..a50fd9289e3 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -0,0 +1,190 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/params/banner.json b/adapters/smaato/smaatotest/params/banner.json new file mode 100644 index 00000000000..a84c44d4d8e --- /dev/null +++ b/adapters/smaato/smaatotest/params/banner.json @@ -0,0 +1,4 @@ +{ + "publisherId": "1100042525", + "adspaceId": "130563103" +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json new file mode 100644 index 00000000000..6d4990e9ea4 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -0,0 +1,166 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"badmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-ext-req.json new file mode 100644 index 00000000000..0c970fc5bad --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-ext-req.json @@ -0,0 +1,54 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString" + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json new file mode 100644 index 00000000000..b9560f0f9ca --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [], + "site": { + "page": "prebid.org", + "publisher": { + "id": "1100042525" + } + }, + "ext": { + "client": "prebid_server_0.1" + } + } + } + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "No sizes provided for Banner []", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 0. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json new file mode 100644 index 00000000000..9e65fce1c3e --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "gender": "M", + "ext": { + "data": { + "keywords":"a,b", + "gender": "M", + "yob": "", + "geo": { + "country": "ca" + } + }, + "consent":"yes" + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field userExtData.data.yob of type int64", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json new file mode 100644 index 00000000000..7f05b2dff14 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "gender": "M", + "ext": 99 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info.json new file mode 100644 index 00000000000..9e0ccfdcdde --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "prebid.org", + "publisher": { + "id": "1100042525" + } + }, + "ext": { + "client": "prebid_server_0.1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/no-imp-req.json b/adapters/smaato/smaatotest/supplemental/no-imp-req.json new file mode 100644 index 00000000000..bfaf51e6ea8 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-imp-req.json @@ -0,0 +1,17 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "publisher": { + "id": "1" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "no impressions in bid request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index 67689d1ab1a..fb0607646ee 100755 --- a/config/config.go +++ b/config/config.go @@ -845,6 +845,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1") + v.SetDefault("adapters.smaato.endpoint", "https://prebid.ad.smaato.net/oapi/prebid") v.SetDefault("adapters.smartadserver.endpoint", "https://ssb.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") diff --git a/docs/bidders/smaato.md b/docs/bidders/smaato.md new file mode 100644 index 00000000000..881f8f2ab54 --- /dev/null +++ b/docs/bidders/smaato.md @@ -0,0 +1,42 @@ + +# Smaato Bidder + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +### Description + +Please contact Smaato Support or prebid@smaato.com to get set up with a publisherId and adspaceId. + +### Test Parameters: + +Following example includes sample `imp` object with publisherId and adSlot which can be used to test Smaato Adapter + +``` +"imp":[ + { + "id":“1C86242D-9535-47D6-9576-7B1FE87F282C, + "banner":{ + "format":[ + { + "w":300, + "h":50 + }, + { + "w":300, + "h":250 + } + ] + }, + "ext":{ + "smaato":{ + "publisherId":"100042525", + "adspaceId":"130563103" + } + } + } + ] +``` diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 2ecddb83cfc..207a7a9b9e9 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -64,6 +64,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smaato" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/somoaudience" @@ -154,6 +155,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), openrtb_ext.BidderSharethrough: sharethrough.NewSharethroughBidder(cfg.Adapters[string(openrtb_ext.BidderSharethrough)].Endpoint), + openrtb_ext.BidderSmaato: smaato.NewSmaatoBidder(cfg.Adapters[string(openrtb_ext.BidderSmaato)].Endpoint), openrtb_ext.BidderSmartadserver: smartadserver.NewSmartadserverBidder(cfg.Adapters[string(openrtb_ext.BidderSmartadserver)].Endpoint), openrtb_ext.BidderSmartRTB: smartrtb.NewSmartRTBBidder(cfg.Adapters[string(openrtb_ext.BidderSmartRTB)].Endpoint), openrtb_ext.BidderSomoaudience: somoaudience.NewSomoaudienceBidder(cfg.Adapters[string(openrtb_ext.BidderSomoaudience)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 62fb9750616..ee0f40903e0 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -80,6 +80,7 @@ const ( BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" + BidderSmaato BidderName = "smaato" BidderSmartadserver BidderName = "smartadserver" BidderSmartRTB BidderName = "smartrtb" BidderSomoaudience BidderName = "somoaudience" @@ -162,6 +163,7 @@ var BidderMap = map[string]BidderName{ "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, "sharethrough": BidderSharethrough, + "smaato": BidderSmaato, "smartadserver": BidderSmartadserver, "smartrtb": BidderSmartRTB, "somoaudience": BidderSomoaudience, diff --git a/openrtb_ext/imp_smaato.go b/openrtb_ext/imp_smaato.go new file mode 100644 index 00000000000..10de97fb017 --- /dev/null +++ b/openrtb_ext/imp_smaato.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpSmaato defines the contract for bidrequest.imp[i].ext.smaato +// PublisherId and AdSpaceId are mandatory parameters, others are optional parameters +// AdSpaceId is identifier for specific ad placement or ad tag +type ExtImpSmaato struct { + PublisherID string `json:"publisherId"` + AdSpaceID string `json:"adspaceId"` +} diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml new file mode 100644 index 00000000000..662603febdb --- /dev/null +++ b/static/bidder-info/smaato.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "prebid@smaato.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-params/smaato.json b/static/bidder-params/smaato.json new file mode 100644 index 00000000000..aa91c4bacc5 --- /dev/null +++ b/static/bidder-params/smaato.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Smaato Adapter Params", + "description": "A schema which validates params accepted by the Smaato adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "A unique identifier for this impression within the context of the bid request" + }, + "adspaceId": { + "type": "string", + "description": "Identifier for specific ad placement is SOMA `adspaceId`" + } + }, + "required": ["publisherId","adspaceId"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 32ab2e730eb..22b215c3132 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -93,6 +93,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderMobileFuse: true, openrtb_ext.BidderOrbidder: true, openrtb_ext.BidderPubnative: true, + openrtb_ext.BidderSmaato: true, openrtb_ext.BidderTappx: true, openrtb_ext.BidderYeahmobi: true, } From 7615d472149c9dd7ffd06360e54792cd54a0d51f Mon Sep 17 00:00:00 2001 From: Adprime <64427228+Adprime@users.noreply.github.com> Date: Thu, 6 Aug 2020 19:43:30 +0300 Subject: [PATCH 157/603] New Adprime adapter (#1418) Co-authored-by: Aiholkin --- adapters/adprime/adprime.go | 142 ++++++++++++++++++ adapters/adprime/adprime_test.go | 12 ++ .../adprimetest/exemplary/simple-banner.json | 134 +++++++++++++++++ .../adprimetest/exemplary/simple-video.json | 119 +++++++++++++++ .../exemplary/simple-web-banner.json | 133 ++++++++++++++++ .../adprime/adprimetest/params/banner.json | 3 + .../adprimetest/params/race/banner.json | 3 + .../adprimetest/params/race/video.json | 3 + .../adprime/adprimetest/params/video.json | 3 + .../adprimetest/supplemental/bad-imp-ext.json | 42 ++++++ .../supplemental/bad_response.json | 85 +++++++++++ .../supplemental/no-imp-ext-1.json | 39 +++++ .../supplemental/no-imp-ext-2.json | 39 +++++ .../adprimetest/supplemental/status-204.json | 79 ++++++++++ .../adprimetest/supplemental/status-404.json | 85 +++++++++++ adapters/adprime/params_test.go | 46 ++++++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adprime.go | 6 + static/bidder-info/adprime.yaml | 11 ++ static/bidder-params/adprime.json | 14 ++ usersync/usersyncers/syncer_test.go | 1 + 23 files changed, 1004 insertions(+) create mode 100644 adapters/adprime/adprime.go create mode 100644 adapters/adprime/adprime_test.go create mode 100644 adapters/adprime/adprimetest/exemplary/simple-banner.json create mode 100644 adapters/adprime/adprimetest/exemplary/simple-video.json create mode 100644 adapters/adprime/adprimetest/exemplary/simple-web-banner.json create mode 100644 adapters/adprime/adprimetest/params/banner.json create mode 100644 adapters/adprime/adprimetest/params/race/banner.json create mode 100644 adapters/adprime/adprimetest/params/race/video.json create mode 100644 adapters/adprime/adprimetest/params/video.json create mode 100644 adapters/adprime/adprimetest/supplemental/bad-imp-ext.json create mode 100644 adapters/adprime/adprimetest/supplemental/bad_response.json create mode 100644 adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json create mode 100644 adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json create mode 100644 adapters/adprime/adprimetest/supplemental/status-204.json create mode 100644 adapters/adprime/adprimetest/supplemental/status-404.json create mode 100644 adapters/adprime/params_test.go create mode 100644 openrtb_ext/imp_adprime.go create mode 100644 static/bidder-info/adprime.yaml create mode 100644 static/bidder-params/adprime.json diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go new file mode 100644 index 00000000000..007d3c86570 --- /dev/null +++ b/adapters/adprime/adprime.go @@ -0,0 +1,142 @@ +package adprime + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// AdprimeAdapter struct +type AdprimeAdapter struct { + URI string +} + +// NewAdprimeBidder Initializes the Bidder +func NewAdprimeBidder(endpoint string) *AdprimeAdapter { + return &AdprimeAdapter{ + URI: endpoint, + } +} + +type adprimeParams struct { + TagID string `json:"TagID"` +} + +// MakeRequests create bid request for adprime demand +func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var err error + var tagID string + + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb.Imp{imp} + + tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") + if err != nil { + errs = append(errs, err) + continue + } + + reqCopy.Imp[0].TagID = tagID + + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + } + return adapterRequests, errs +} + +func (a *AdprimeAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { + + var errs []error + + reqJSON, err := json.Marshal(request) + + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }, errs +} + +// MakeBids makes the bids +func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusNotFound { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Page not found: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType, nil + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + } +} diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go new file mode 100644 index 00000000000..2d3ee9b2b3f --- /dev/null +++ b/adapters/adprime/adprime_test.go @@ -0,0 +1,12 @@ +package adprime + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adprimeAdapter := NewAdprimeBidder("http://delta.adprime.com/?c=o&m=ortb") + adapterstest.RunJSONBidderTest(t, "adprimetest", adprimeAdapter) +} diff --git a/adapters/adprime/adprimetest/exemplary/simple-banner.json b/adapters/adprime/adprimetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..076175c6274 --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/simple-banner.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adprime/adprimetest/exemplary/simple-video.json b/adapters/adprime/adprimetest/exemplary/simple-video.json new file mode 100644 index 00000000000..3e61c4dddd1 --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/simple-video.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "288" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "tagid": "288", + "ext": { + "bidder": { + "TagID": "288" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adprime/adprimetest/exemplary/simple-web-banner.json b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..a955854fb31 --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/banner.json b/adapters/adprime/adprimetest/params/banner.json new file mode 100644 index 00000000000..e3f4cb7605a --- /dev/null +++ b/adapters/adprime/adprimetest/params/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "1" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/banner.json b/adapters/adprime/adprimetest/params/race/banner.json new file mode 100644 index 00000000000..e3f4cb7605a --- /dev/null +++ b/adapters/adprime/adprimetest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "1" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/video.json b/adapters/adprime/adprimetest/params/race/video.json new file mode 100644 index 00000000000..c8d14757903 --- /dev/null +++ b/adapters/adprime/adprimetest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "288" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/video.json b/adapters/adprime/adprimetest/params/video.json new file mode 100644 index 00000000000..c8d14757903 --- /dev/null +++ b/adapters/adprime/adprimetest/params/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "288" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..a95c56e8426 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "adprime": { + "TagID": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, +"expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } +] +} diff --git a/adapters/adprime/adprimetest/supplemental/bad_response.json b/adapters/adprime/adprimetest/supplemental/bad_response.json new file mode 100644 index 00000000000..329e9c7269f --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/bad_response.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json new file mode 100644 index 00000000000..1e38dbe4541 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json new file mode 100644 index 00000000000..f9759fae8ff --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/status-204.json b/adapters/adprime/adprimetest/supplemental/status-204.json new file mode 100644 index 00000000000..44ee59d4d28 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }] +} diff --git a/adapters/adprime/adprimetest/supplemental/status-404.json b/adapters/adprime/adprimetest/supplemental/status-404.json new file mode 100644 index 00000000000..c2b303f0cb4 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/status-404.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://delta.adprime.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Page not found: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adprime/params_test.go b/adapters/adprime/params_test.go new file mode 100644 index 00000000000..05adad5c4ff --- /dev/null +++ b/adapters/adprime/params_test.go @@ -0,0 +1,46 @@ +package adprime + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// TestValidParams makes sure that the adprime schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdprime, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adprime params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adprime schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdprime, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"TagID": "1"}`, +} + +var invalidParams = []string{ + `{"id": "123"}`, + `{"tagid": "123"}`, + `{"TagID": 16}`, +} diff --git a/config/config.go b/config/config.go index fb0607646ee..9663b021b5b 100755 --- a/config/config.go +++ b/config/config.go @@ -797,6 +797,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") + v.SetDefault("adapters.adprime.endpoint", "http://delta.adprime.com/?c=o&m=ortb") v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 207a7a9b9e9..d056de664b7 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adpone" + "github.com/prebid/prebid-server/adapters/adprime" "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" @@ -106,6 +107,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdOcean: adocean.NewAdOceanBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdOcean))].Endpoint), openrtb_ext.BidderAdoppler: adoppler.NewAdopplerBidder(cfg.Adapters[string(openrtb_ext.BidderAdoppler)].Endpoint), openrtb_ext.BidderAdpone: adpone.NewAdponeBidder(cfg.Adapters[string(openrtb_ext.BidderAdpone)].Endpoint), + openrtb_ext.BidderAdprime: adprime.NewAdprimeBidder(cfg.Adapters[string(openrtb_ext.BidderAdprime)].Endpoint), openrtb_ext.BidderAdtarget: adtarget.NewAdtargetBidder(cfg.Adapters[string(openrtb_ext.BidderAdtarget)].Endpoint), openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), openrtb_ext.BidderAdvangelists: advangelists.NewAdvangelistsBidder(cfg.Adapters[string(openrtb_ext.BidderAdvangelists)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ee0f40903e0..761f53d441e 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -34,6 +34,7 @@ const ( BidderAdman BidderName = "adman" BidderAdmixer BidderName = "admixer" BidderAdOcean BidderName = "adocean" + BidderAdprime BidderName = "adprime" BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" @@ -116,6 +117,7 @@ var BidderMap = map[string]BidderName{ "adman": BidderAdman, "admixer": BidderAdmixer, "adocean": BidderAdOcean, + "adprime": BidderAdprime, "adpone": BidderAdpone, "adtarget": BidderAdtarget, "adtelligent": BidderAdtelligent, diff --git a/openrtb_ext/imp_adprime.go b/openrtb_ext/imp_adprime.go new file mode 100644 index 00000000000..a089b818b56 --- /dev/null +++ b/openrtb_ext/imp_adprime.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpAdprime defines adprime specifiec param +type ExtImpAdprime struct { + TagID string `json:"TagID"` +} diff --git a/static/bidder-info/adprime.yaml b/static/bidder-info/adprime.yaml new file mode 100644 index 00000000000..9759ed63be7 --- /dev/null +++ b/static/bidder-info/adprime.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "rafal@adprime.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/adprime.json b/static/bidder-params/adprime.json new file mode 100644 index 00000000000..d527056597d --- /dev/null +++ b/static/bidder-params/adprime.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adprime Adapter Params", + "description": "A schema which validates params accepted by the Adprime adapter", + + "type": "object", + "properties": { + "TagID": { + "type": "string", + "description": "An ID which identifies the adprime ad tag" + } + }, + "required" : [ "TagID" ] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 22b215c3132..9197ed9507d 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -96,6 +96,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderSmaato: true, openrtb_ext.BidderTappx: true, openrtb_ext.BidderYeahmobi: true, + openrtb_ext.BidderAdprime: true, } for bidder, config := range cfg.Adapters { From a7aaa97af15618f1b4cb7de3cb38866213c41028 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 6 Aug 2020 14:21:01 -0400 Subject: [PATCH 158/603] Separate "debug" behavior from "billable" behavior (#1387) --- exchange/bidder.go | 2 +- exchange/bidder_test.go | 48 ----- exchange/exchange.go | 99 ++++----- exchange/exchange_test.go | 190 ++++++++++++++++-- exchange/utils.go | 98 ++++++--- exchange/utils_test.go | 412 ++++++++++++++++++++++++++++++++------ openrtb_ext/request.go | 1 + 7 files changed, 646 insertions(+), 204 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 7c39b72b348..decad8ccf2f 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -150,7 +150,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi for i := 0; i < len(reqData); i++ { httpInfo := <-responseChannel // If this is a test bid, capture debugging info from the requests. - if request.Test == 1 { + if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 1a27b72aa12..7ae96c09b93 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -938,54 +938,6 @@ func TestSuccessfulResponseLogging(t *testing.T) { } } -// TestServerCallDebugging makes sure that we log the server calls made by the Bidder on test bids. -func TestServerCallDebugging(t *testing.T) { - respBody := "{\"bid\":false}" - respStatus := 200 - server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) - defer server.Close() - - reqBody := "{\"key\":\"val\"}" - reqUrl := server.URL - bidderImpl := &goodSingleBidder{ - httpRequest: &adapters.RequestData{ - Method: "POST", - Uri: reqUrl, - Body: []byte(reqBody), - Headers: http.Header{}, - }, - } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() - - bids, _ := bidder.requestBid( - context.Background(), - &openrtb.BidRequest{ - Test: 1, - }, - "test", - 1.0, - currencyConverter.Rates(), - &adapters.ExtraRequestInfo{}, - ) - - if len(bids.httpCalls) != 1 { - t.Errorf("We should log the server call if this is a test bid. Got %d", len(bids.httpCalls)) - } - if bids.httpCalls[0].Uri != reqUrl { - t.Errorf("Wrong httpcalls URI. Expected %s, got %s", reqUrl, bids.httpCalls[0].Uri) - } - if bids.httpCalls[0].RequestBody != reqBody { - t.Errorf("Wrong httpcalls RequestBody. Expected %s, got %s", reqBody, bids.httpCalls[0].RequestBody) - } - if bids.httpCalls[0].ResponseBody != respBody { - t.Errorf("Wrong httpcalls ResponseBody. Expected %s, got %s", respBody, bids.httpCalls[0].ResponseBody) - } - if bids.httpCalls[0].Status != respStatus { - t.Errorf("Wrong httpcalls Status. Expected %d, got %d", respStatus, bids.httpCalls[0].Status) - } -} - func TestMobileNativeTypes(t *testing.T) { respBody := "{\"bid\":false}" respStatus := 200 diff --git a/exchange/exchange.go b/exchange/exchange.go index 5001e495440..ad591f57794 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -27,6 +27,10 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" ) +type ContextKey string + +const DebugContextKey = ContextKey("debugInfo") + // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines. type Exchange interface { // HoldAuction executes an OpenRTB v2.5 Auction. @@ -86,12 +90,25 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con } func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) { - // Snapshot of resolved bid request for debug if test request - resolvedRequest, err := buildResolvedRequest(bidRequest) + + requestExt, err := extractBidRequestExt(bidRequest) if err != nil { - glog.Errorf("Error marshalling bid request for debug: %v", err) + return nil, err + } + + shouldCacheBids, shouldCacheVAST := getExtCacheInfo(requestExt) + targData := getExtTargetData(requestExt, shouldCacheBids, shouldCacheVAST) + if targData != nil { + targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() + } + + debugInfo := getDebugInfo(bidRequest, requestExt) + if debugInfo { + ctx = e.makeDebugContext(ctx, debugInfo) } + bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExt) + for _, impInRequest := range bidRequest.Imp { var impLabels pbsmetrics.ImpLabels = pbsmetrics.ImpLabels{ BannerImps: impInRequest.Banner != nil, @@ -104,46 +121,16 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) e.me.RecordRequestPrivacy(privacyLabels) // List of bidders we have requests for. liveAdapters := listBiddersWithRequests(cleanRequests) - // Process the request to check for targeting parameters. - var targData *targetData - shouldCacheBids := false - shouldCacheVAST := false - var bidAdjustmentFactors map[string]float64 - var requestExt openrtb_ext.ExtRequest - if len(bidRequest.Ext) > 0 { - err := json.Unmarshal(bidRequest.Ext, &requestExt) - if err != nil { - return nil, fmt.Errorf("Error decoding Request.ext : %s", err.Error()) - } - bidAdjustmentFactors = requestExt.Prebid.BidAdjustmentFactors - if requestExt.Prebid.Cache != nil { - shouldCacheBids = requestExt.Prebid.Cache.Bids != nil - shouldCacheVAST = requestExt.Prebid.Cache.VastXML != nil - } - - if requestExt.Prebid.Targeting != nil { - targData = &targetData{ - priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, - includeWinners: requestExt.Prebid.Targeting.IncludeWinners, - includeBidderKeys: requestExt.Prebid.Targeting.IncludeBidderKeys, - includeCacheBids: shouldCacheBids, - includeCacheVast: shouldCacheVAST, - includeFormat: requestExt.Prebid.Targeting.IncludeFormat, - } - targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() - } - } - // If we need to cache bids, then it will take some time to call prebid cache. // We should reduce the amount of time the bidders have, to compensate. - auctionCtx, cancel := e.makeAuctionContext(ctx, shouldCacheBids) //Why no context for `shouldCacheVast`? + auctionCtx, cancel := e.makeAuctionContext(ctx, shouldCacheBids) defer cancel() // Get currency rates conversions for the auction @@ -180,7 +167,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } if debugLog != nil && debugLog.Enabled { - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, debugInfo, errs) if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { @@ -205,7 +192,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } // Build the response - return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, bidResponseExt, errs) + return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, adapterExtra, auc, bidResponseExt, errs) } type DealTierInfo struct { @@ -284,6 +271,11 @@ func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo, bidCategory m } } +func (e *exchange) makeDebugContext(ctx context.Context, debugInfo bool) (debugCtx context.Context) { + debugCtx = context.WithValue(ctx, DebugContextKey, debugInfo) + return +} + func (e *exchange) makeAuctionContext(ctx context.Context, needsCache bool) (auctionCtx context.Context, cancel context.CancelFunc) { auctionCtx = ctx cancel = func() {} @@ -445,7 +437,7 @@ func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, resolvedRequest json.RawMessage, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, errList []error) (*openrtb.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, errList []error) (*openrtb.BidResponse, error) { bidResponse := new(openrtb.BidResponse) bidResponse.ID = bidRequest.ID @@ -469,7 +461,12 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ bidResponse.SeatBid = seatBids if bidResponseExt == nil { - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errList) + contextDebugValue := ctx.Value(DebugContextKey) + var debugInfo bool + if contextDebugValue != nil { + debugInfo = contextDebugValue.(bool) + } + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, debugInfo, errList) } buffer := &bytes.Buffer{} enc := json.NewEncoder(buffer) @@ -480,7 +477,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ return bidResponse, err } -func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -491,6 +488,8 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest dedupe := make(map[string]bidDedupe) + // applyCategoryMapping doesn't get called unless + // requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil brandCatExt := requestExt.Prebid.Targeting.IncludeBrandCategory //If ext.prebid.targeting.includebrandcategory is present in ext then competitive exclusion feature is on. @@ -656,24 +655,22 @@ func getPrimaryAdServer(adServerId int) (string, error) { } // Extract all the data from the SeatBids and build the ExtBidResponse -func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, req *openrtb.BidRequest, resolvedRequest json.RawMessage, errList []error) *openrtb_ext.ExtBidResponse { +func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, req *openrtb.BidRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse { bidResponseExt := &openrtb_ext.ExtBidResponse{ Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)), ResponseTimeMillis: make(map[openrtb_ext.BidderName]int, len(adapterBids)), RequestTimeoutMillis: req.TMax, } - if req.Test == 1 { + if debugInfo { bidResponseExt.Debug = &openrtb_ext.ExtResponseDebug{ - HttpCalls: make(map[openrtb_ext.BidderName][]*openrtb_ext.ExtHttpCall), - } - if err := json.Unmarshal(resolvedRequest, &bidResponseExt.Debug.ResolvedRequest); err != nil { - glog.Errorf("Error unmarshalling bid request snapshot: %v", err) + HttpCalls: make(map[openrtb_ext.BidderName][]*openrtb_ext.ExtHttpCall), + ResolvedRequest: req, } } for bidderName, responseExtra := range adapterExtra { - if req.Test == 1 { + if debugInfo { bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } // Only make an entry for bidder errors if the bidder reported any. @@ -774,14 +771,6 @@ func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auc *auction) (openrtb_ext.E return cacheInfo, found } -// Returns a snapshot of resolved bid request for debug if test field is set in the incomming request -func buildResolvedRequest(bidRequest *openrtb.BidRequest) (json.RawMessage, error) { - if bidRequest.Test == 1 { - return json.Marshal(bidRequest) - } - return nil, nil -} - func listBiddersWithRequests(cleanRequests map[openrtb_ext.BidderName]*openrtb.BidRequest) []openrtb_ext.BidderName { liveAdapters := make([]openrtb_ext.BidderName, len(cleanRequests)) i := 0 diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 96f740de23a..7da7b62e70b 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -22,6 +22,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" metricsConf "github.com/prebid/prebid-server/pbsmetrics/config" + metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config" pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" @@ -112,9 +113,6 @@ func TestCharacterEscape(t *testing.T) { Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`), } - //resolvedRequest json.RawMessage - resolvedRequest := json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`) - //adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1) adapterExtra["appnexus"] = &seatResponseExtra{ @@ -126,7 +124,7 @@ func TestCharacterEscape(t *testing.T) { var errList []error /* 4) Build bid response */ - bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList) + bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, errList) /* 5) Assert we have no errors and one '&' character as we are supposed to */ if err != nil { @@ -140,6 +138,137 @@ func TestCharacterEscape(t *testing.T) { } } +// TestDebugBehaviour asserts the HttpCalls object is included inside the json "debug" field of the bidResponse extension when the +// openrtb.BidRequest "Test" value is set to 1 or the openrtb.BidRequest.Ext.Debug boolean field is set to true +func TestDebugBehaviour(t *testing.T) { + + // Define test cases + type inTest struct { + test int8 + debug bool + } + type outTest struct { + debugInfoIncluded bool + } + type aTest struct { + desc string + in inTest + out outTest + } + testCases := []aTest{ + { + desc: "test flag equals zero, ext debug flag false, no debug info expected", + in: inTest{test: 0, debug: false}, + out: outTest{debugInfoIncluded: false}, + }, + { + desc: "test flag equals zero, ext debug flag true, debug info expected", + in: inTest{test: 0, debug: true}, + out: outTest{debugInfoIncluded: true}, + }, + { + desc: "test flag equals 1, ext debug flag false, debug info expected", + in: inTest{test: 1, debug: false}, + out: outTest{debugInfoIncluded: true}, + }, + { + desc: "test flag equals 1, ext debug flag true, debug info expected", + in: inTest{test: 1, debug: true}, + out: outTest{debugInfoIncluded: true}, + }, + { + desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", + in: inTest{test: 2, debug: false}, + out: outTest{debugInfoIncluded: false}, + }, + { + desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + }, + } + + // Set up test + noBidServer := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + bidRequest := &openrtb.BidRequest{ + ID: "some-request-id", + Imp: []openrtb.Imp{{ + ID: "some-impression-id", + Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + AT: 1, + TMax: 500, + } + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{}, + } + + e := new(exchange) + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus), + } + e.cache = &wellBehavedCache{} + e.me = &metricsConf.DummyMetricsEngine{} + e.gDPR = gdpr.AlwaysAllow{} + e.currencyConverter = currencies.NewRateConverterDefault() + + // Run tests + for _, test := range testCases { + bidRequest.Test = test.in.test + + if test.in.debug { + bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`) + } else { + bidRequest.Ext = nil + } + + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) + + // Assert no HoldAuction error + assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) + assert.NotNilf(t, outBidResponse.Ext, "%s. outBidResponse.Ext should not be nil \n", test.desc) + + actualExt := &openrtb_ext.ExtBidResponse{} + err = json.Unmarshal(outBidResponse.Ext, actualExt) + assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext) + + if test.out.debugInfoIncluded { + assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug) + + // Assert "Debug fields + assert.Greater(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty\n", test.desc) + assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["appnexus"][0].Uri, "%s. ext.debug.httpcalls array should not be empty\n", test.desc) + assert.NotNilf(t, actualExt.Debug.ResolvedRequest, "%s. ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug) + + // If not nil, assert bid extension + if test.in.debug { + diffJson(t, test.desc, bidRequest.Ext, actualExt.Debug.ResolvedRequest.Ext) + } + } + } +} + func TestGetBidCacheInfo(t *testing.T) { testUUID := "CACHE_UUID_1234" testExternalCacheHost := "https://www.externalprebidcache.net" @@ -230,9 +359,6 @@ func TestGetBidCacheInfo(t *testing.T) { }, } - //resolvedRequest json.RawMessage - resolvedRequest := json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`) - //adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ bidderName: { @@ -278,7 +404,7 @@ func TestGetBidCacheInfo(t *testing.T) { var errList []error /* 4) Build bid response */ - bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, nil, errList) + bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, errList) /* 5) Assert we have no errors and the bid response we expected*/ assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") @@ -342,8 +468,6 @@ func TestBidResponseCurrency(t *testing.T) { Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`), } - resolvedRequest := json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`) - adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ "appnexus": {ResponseTimeMillis: 5}, } @@ -449,7 +573,7 @@ func TestBidResponseCurrency(t *testing.T) { // Run tests for i := range testCases { - actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList) + actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, nil, errList) assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err)) assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext)) } @@ -702,6 +826,38 @@ func TestTimeoutComputation(t *testing.T) { } } +func TestSetDebugContextKey(t *testing.T) { + // Test cases + testCases := []struct { + desc string + inDebugInfo bool + expectedDebugInfo bool + }{ + { + desc: "debugInfo flag on, we expect to find DebugContextKey key in context", + inDebugInfo: true, + expectedDebugInfo: true, + }, + { + desc: "debugInfo flag off, we don't expect to find DebugContextKey key in context", + inDebugInfo: false, + expectedDebugInfo: false, + }, + } + + // Setup test + ex := exchange{} + + // Run tests + for _, test := range testCases { + auctionCtx := ex.makeDebugContext(context.Background(), test.inDebugInfo) + + debugInfo := auctionCtx.Value(DebugContextKey) + assert.NotNil(t, debugInfo, "%s. Flag set, `debugInfo` shouldn't be nil") + assert.Equal(t, test.expectedDebugInfo, debugInfo.(bool), "Desc: %s. Incorrect value mapped to DebugContextKey(`debugInfo`) in the context\n", test.desc) + } +} + // TestExchangeJSON executes tests for all the *.json files in exchangetest. func TestExchangeJSON(t *testing.T) { if specFiles, err := ioutil.ReadDir("./exchangetest"); err == nil { @@ -974,7 +1130,7 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1029,7 +1185,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -1081,7 +1237,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1163,7 +1319,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -1229,7 +1385,7 @@ func TestCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -1340,7 +1496,7 @@ func TestBidRejectionErrors(t *testing.T) { adapterBids[bidderName] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, test.reqExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &test.reqExt, adapterBids, categoriesFetcher, targData) if len(test.expectedCatDur) > 0 { // Bid deduplication case diff --git a/exchange/utils.go b/exchange/utils.go index bc1b555e507..97fae7b78ca 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -22,12 +22,8 @@ import ( func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) - if len(req.Prebid.SChains) == 0 { - return bidderToSChains, nil - } - - for _, schainWrapper := range req.Prebid.SChains { - if schainWrapper != nil && len(schainWrapper.Bidders) > 0 { + if req != nil { + for _, schainWrapper := range req.Prebid.SChains { for _, bidder := range schainWrapper.Bidders { if _, present := bidderToSChains[bidder]; present { return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ @@ -49,6 +45,7 @@ func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext // 3. BidRequest.User.BuyerUID will be set to that Bidder's ID. func cleanOpenRTBRequests(ctx context.Context, orig *openrtb.BidRequest, + requestExt *openrtb_ext.ExtRequest, usersyncs IdFetcher, blables map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, labels pbsmetrics.Labels, @@ -66,7 +63,7 @@ func cleanOpenRTBRequests(ctx context.Context, return } - requestsByBidder, errs = splitBidRequest(orig, impsByBidder, aliases, usersyncs, blables, labels) + requestsByBidder, errs = splitBidRequest(orig, requestExt, impsByBidder, aliases, usersyncs, blables, labels) gdpr := extractGDPR(orig, usersyncIfAmbiguous) consent := extractConsent(orig) @@ -125,6 +122,7 @@ func cleanOpenRTBRequests(ctx context.Context, } func splitBidRequest(req *openrtb.BidRequest, + requestExt *openrtb_ext.ExtRequest, impsByBidder map[string][]openrtb.Imp, aliases map[string]string, usersyncs IdFetcher, @@ -137,20 +135,16 @@ func splitBidRequest(req *openrtb.BidRequest, return nil, []error{err} } - var requestExt openrtb_ext.ExtRequest var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain - if len(req.Ext) > 0 { - err := json.Unmarshal(req.Ext, &requestExt) - if err != nil { - return nil, []error{err} - } - sChainsByBidder, err = BidderToPrebidSChains(&requestExt) - if err != nil { - return nil, []error{err} - } - } else { - sChainsByBidder = make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) + sChainsByBidder, err = BidderToPrebidSChains(requestExt) + if err != nil { + return nil, []error{err} + } + + reqExt, err := getExtJson(req, requestExt) + if err != nil { + return nil, []error{err} } for bidder, imps := range impsByBidder { @@ -174,23 +168,21 @@ func splitBidRequest(req *openrtb.BidRequest, reqCopy.Imp = imps prepareSource(&reqCopy, bidder, sChainsByBidder) - prepareExt(&reqCopy, &requestExt) + reqCopy.Ext = reqExt requestsByBidder[openrtb_ext.BidderName(bidder)] = &reqCopy } return requestsByBidder, nil } -func prepareExt(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) { - if len(req.Ext) == 0 { - return +func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { + if len(req.Ext) == 0 || unpackedExt == nil { + return json.RawMessage(``), nil } + extCopy := *unpackedExt extCopy.Prebid.SChains = nil - reqExt, err := json.Marshal(extCopy) - if err == nil { - req.Ext = reqExt - } + return json.Marshal(extCopy) } func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { @@ -434,3 +426,55 @@ func randomizeList(list []openrtb_ext.BidderName) { list[i], list[j] = list[j], list[i] } } + +func extractBidRequestExt(bidRequest *openrtb.BidRequest) (*openrtb_ext.ExtRequest, error) { + requestExt := &openrtb_ext.ExtRequest{} + + if bidRequest == nil { + return requestExt, fmt.Errorf("Error bidRequest should not be nil") + } + + if len(bidRequest.Ext) > 0 { + err := json.Unmarshal(bidRequest.Ext, &requestExt) + if err != nil { + return requestExt, fmt.Errorf("Error decoding Request.ext : %s", err.Error()) + } + } + return requestExt, nil +} + +func getExtCacheInfo(requestExt *openrtb_ext.ExtRequest) (shouldCacheBids bool, shouldCacheVAST bool) { + if requestExt != nil && requestExt.Prebid.Cache != nil { + shouldCacheBids = requestExt.Prebid.Cache.Bids != nil + shouldCacheVAST = requestExt.Prebid.Cache.VastXML != nil + } + return +} + +func getExtTargetData(requestExt *openrtb_ext.ExtRequest, shouldCacheBids bool, shouldCacheVAST bool) *targetData { + var targData *targetData + + if requestExt != nil && requestExt.Prebid.Targeting != nil { + targData = &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: requestExt.Prebid.Targeting.IncludeWinners, + includeBidderKeys: requestExt.Prebid.Targeting.IncludeBidderKeys, + includeCacheBids: shouldCacheBids, + includeCacheVast: shouldCacheVAST, + includeFormat: requestExt.Prebid.Targeting.IncludeFormat, + } + } + return targData +} + +func getDebugInfo(bidRequest *openrtb.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { + return (bidRequest != nil && bidRequest.Test == 1) || (requestExt != nil && requestExt.Prebid.Debug) +} + +func getExtBidAdjustmentFactors(requestExt *openrtb_ext.ExtRequest) map[string]float64 { + var bidAdjustmentFactors map[string]float64 + if requestExt != nil { + bidAdjustmentFactors = requestExt.Prebid.BidAdjustmentFactors + } + return bidAdjustmentFactors +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 608e6a17a10..3b919d3da56 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -3,6 +3,7 @@ package exchange import ( "context" "encoding/json" + "fmt" "testing" "github.com/mxmCherry/openrtb" @@ -79,7 +80,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -141,7 +142,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) @@ -185,7 +186,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { req := newBidRequest(t) req.Regs = &openrtb.Regs{COPPA: test.coppa} - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) result := results["appnexus"] assert.Nil(t, errs) @@ -202,77 +203,92 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { func TestCleanOpenRTBRequestsSChain(t *testing.T) { testCases := []struct { - description string - inSourceExt json.RawMessage - inExt json.RawMessage - outSourceExt json.RawMessage - outExt json.RawMessage - hasError bool + description string + inExt json.RawMessage + inSourceExt json.RawMessage + outSourceExt json.RawMessage + outRequestExt json.RawMessage + hasError bool }{ { - description: "Empty root ext and source ext", - inSourceExt: json.RawMessage(``), - inExt: json.RawMessage(``), - outSourceExt: json.RawMessage(``), - outExt: json.RawMessage(``), - hasError: false, + description: "Empty root ext and source ext, nil unmarshaled ext", + inExt: nil, + inSourceExt: json.RawMessage(``), + outSourceExt: json.RawMessage(``), + outRequestExt: json.RawMessage(``), + hasError: false, }, { - description: "No schains in root ext and empty source ext", - inSourceExt: json.RawMessage(``), - inExt: json.RawMessage(`{"prebid":{"schains":[]}}`), - outSourceExt: json.RawMessage(``), - outExt: json.RawMessage(`{"prebid":{}}`), - hasError: false, + description: "Empty root ext, source ext, and unmarshaled ext", + inExt: json.RawMessage(``), + inSourceExt: json.RawMessage(``), + outSourceExt: json.RawMessage(``), + outRequestExt: json.RawMessage(``), + hasError: false, }, { - description: "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains", - inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), - outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), - outExt: json.RawMessage(`{"prebid":{}}`), - hasError: false, + description: "No schains in root ext and empty source ext. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[]}}`), + outSourceExt: json.RawMessage(``), + outRequestExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, }, { - description: "Use schain for bidder in ext.prebid.schains", - inSourceExt: json.RawMessage(``), - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), - outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), - outExt: json.RawMessage(`{"prebid":{}}`), - hasError: false, + description: "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + outRequestExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, }, { - description: "Use wildcard schain in ext.prebid.schains", - inSourceExt: json.RawMessage(``), - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), - outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), - outExt: json.RawMessage(`{"prebid":{}}`), - hasError: false, + description: "Use schain for bidder in ext.prebid.schains. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outRequestExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, }, { - description: "Use schain for bidder in ext.prebid.schains instead of wildcard", - inSourceExt: json.RawMessage(``), - inExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`), - outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), - outExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`), - hasError: false, + description: "Use wildcard schain in ext.prebid.schains. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outRequestExt: json.RawMessage(`{"prebid":{}}`), + hasError: false, }, { - description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains", - inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), - outSourceExt: nil, - outExt: nil, - hasError: true, + description: "Use schain for bidder in ext.prebid.schains instead of wildcard. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(``), + inExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`), + outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`), + outRequestExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`), + hasError: false, + }, + { + description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains. Unmarshaled ext is equivalent to root ext", + inSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`), + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + outSourceExt: nil, + outRequestExt: nil, + hasError: true, }, } for _, test := range testCases { req := newBidRequest(t) req.Source.Ext = test.inSourceExt - req.Ext = test.inExt - results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{}) + var extRequest *openrtb_ext.ExtRequest + if test.inExt != nil { + req.Ext = test.inExt + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") + extRequest = unmarshaledExt + } + + results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, extRequest, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{}) result := results["appnexus"] if test.hasError == true { @@ -281,11 +297,295 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { } else { assert.Nil(t, errs) assert.Equal(t, test.outSourceExt, result.Source.Ext, test.description+":Source.Ext") - assert.Equal(t, test.outExt, result.Ext, test.description+":Ext") + assert.Equal(t, test.outRequestExt, result.Ext, test.description+":Ext") } } } +func TestExtractBidRequesteExt(t *testing.T) { + testCases := []struct { + desc string + inBidRequest *openrtb.BidRequest + outRequestExt *openrtb_ext.ExtRequest + outError error + }{ + { + desc: "Valid", + inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true}}`)}, + outRequestExt: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}, + outError: nil, + }, + { + desc: "bidRequest nil, we expect an error", + inBidRequest: nil, + outRequestExt: &openrtb_ext.ExtRequest{}, + outError: fmt.Errorf("Error bidRequest should not be nil"), + }, + { + desc: "Non-nil bidRequest with empty Ext, we expect a blank requestExt", + inBidRequest: &openrtb.BidRequest{}, + outRequestExt: &openrtb_ext.ExtRequest{}, + outError: nil, + }, + { + desc: "Non-nil bidRequest with non-empty, invalid Ext, we expect unmarshaling error", + inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`invalid`)}, + outRequestExt: &openrtb_ext.ExtRequest{}, + outError: fmt.Errorf("Error decoding Request.ext : invalid character 'i' looking for beginning of value"), + }, + } + for _, test := range testCases { + actualRequestExt, actualErr := extractBidRequestExt(test.inBidRequest) + + assert.Equal(t, test.outRequestExt, actualRequestExt, "%s. Unexpected RequestExt value. \n", test.desc) + assert.Equal(t, test.outError, actualErr, "%s. Unexpected error value. \n", test.desc) + } +} + +func TestGetExtCacheInfo(t *testing.T) { + testCases := []struct { + desc string + inRequestExt *openrtb_ext.ExtRequest + outCacheBids bool + outCacheVAST bool + }{ + { + desc: "Nil inRequestExt, both cache flags false", + inRequestExt: nil, + outCacheBids: false, + outCacheVAST: false, + }, + { + desc: "Non-nil inRequestExt, nil Cache info, both cache flags false", + inRequestExt: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Cache: nil}}, + outCacheBids: false, + outCacheVAST: false, + }, + { + desc: "Non-nil inRequestExt, non-nil Cache info, both ExtRequestPrebidCacheBids and ExtRequestPrebidCacheVAST nil", + inRequestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Cache: &openrtb_ext.ExtRequestPrebidCache{ + Bids: nil, + VastXML: nil, + }, + }, + }, + outCacheBids: false, + outCacheVAST: false, + }, + { + desc: "Non-nil inRequestExt, non-nil Cache info, both ExtRequestPrebidCacheBids nil, shouldCacheVast true", + inRequestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Cache: &openrtb_ext.ExtRequestPrebidCache{ + Bids: nil, + VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, + }, + }, + }, + outCacheBids: false, + outCacheVAST: true, + }, + { + desc: "Non-nil inRequestExt, non-nil Cache info, both ExtRequestPrebidCacheVAST nil, shouldCacheBids true", + inRequestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Cache: &openrtb_ext.ExtRequestPrebidCache{ + Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, + VastXML: nil, + }, + }, + }, + outCacheBids: true, + outCacheVAST: false, + }, + { + desc: "Non-nil inRequestExt, non-nil Cache info values, both shouldCacheBids and shouldCacheVAST return true", + inRequestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Cache: &openrtb_ext.ExtRequestPrebidCache{ + Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, + VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, + }, + }, + }, + outCacheBids: true, + outCacheVAST: true, + }, + } + for _, test := range testCases { + shouldCacheBids, shouldCacheVAST := getExtCacheInfo(test.inRequestExt) + + assert.Equal(t, test.outCacheBids, shouldCacheBids, "%s. Unexpected shouldCacheBids value. \n", test.desc) + assert.Equal(t, test.outCacheVAST, shouldCacheVAST, "%s. Unexpected shouldCacheVAST value. \n", test.desc) + } +} + +func TestGetExtTargetData(t *testing.T) { + type inTest struct { + requestExt *openrtb_ext.ExtRequest + shouldCacheBids bool + shouldCacheVAST bool + } + type outTest struct { + targetData *targetData + nilTargetData bool + } + testCases := []struct { + desc string + in inTest + out outTest + }{ + { + "nil requestExt, nil outTargetData", + inTest{ + requestExt: nil, + shouldCacheBids: true, + shouldCacheVAST: true, + }, + outTest{targetData: nil, nilTargetData: true}, + }, + { + "Valid requestExt, nil Targeting field, nil outTargetData", + inTest{ + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Targeting: nil, + }, + }, + shouldCacheBids: true, + shouldCacheVAST: true, + }, + outTest{targetData: nil, nilTargetData: true}, + }, + { + "Valid targeting data in requestExt, valid outTargetData", + inTest{ + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Targeting: &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: openrtb_ext.PriceGranularity{ + Precision: 2, + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, + }, + IncludeWinners: true, + IncludeBidderKeys: true, + }, + }, + }, + shouldCacheBids: true, + shouldCacheVAST: true, + }, + outTest{ + targetData: &targetData{ + priceGranularity: openrtb_ext.PriceGranularity{ + Precision: 2, + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, + }, + includeWinners: true, + includeBidderKeys: true, + includeCacheBids: true, + includeCacheVast: true, + }, + nilTargetData: false, + }, + }, + } + for _, test := range testCases { + actualTargetData := getExtTargetData(test.in.requestExt, test.in.shouldCacheBids, test.in.shouldCacheVAST) + + if test.out.nilTargetData { + assert.Nil(t, actualTargetData, "%s. Targeting data should be nil. \n", test.desc) + } else { + assert.NotNil(t, actualTargetData, "%s. Targeting data should NOT be nil. \n", test.desc) + assert.Equal(t, *test.out.targetData, *actualTargetData, "%s. Unexpected targeting data value. \n", test.desc) + } + } +} + +func TestGetDebugInfo(t *testing.T) { + type inTest struct { + bidRequest *openrtb.BidRequest + requestExt *openrtb_ext.ExtRequest + } + testCases := []struct { + desc string + in inTest + out bool + }{ + { + desc: "Nil bid request, nil requestExt", + in: inTest{nil, nil}, + out: false, + }, + { + desc: "bid request test == 0, nil requestExt", + in: inTest{&openrtb.BidRequest{Test: 0}, nil}, + out: false, + }, + { + desc: "Nil bid request, requestExt debug flag false", + in: inTest{nil, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + out: false, + }, + { + desc: "bid request test == 0, requestExt debug flag false", + in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + out: false, + }, + { + desc: "bid request test == 1, requestExt debug flag false", + in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + out: true, + }, + { + desc: "bid request test == 0, requestExt debug flag true", + in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + out: true, + }, + { + desc: "bid request test == 1, requestExt debug flag true", + in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + out: true, + }, + } + for _, test := range testCases { + actualDebugInfo := getDebugInfo(test.in.bidRequest, test.in.requestExt) + + assert.Equal(t, test.out, actualDebugInfo, "%s. Unexpected debug value. \n", test.desc) + } +} + +func TestGetExtBidAdjustmentFactors(t *testing.T) { + testCases := []struct { + desc string + inRequestExt *openrtb_ext.ExtRequest + outBidAdjustmentFactors map[string]float64 + }{ + { + desc: "Nil request ext", + inRequestExt: nil, + outBidAdjustmentFactors: nil, + }, + { + desc: "Non-nil request ext, nil BidAdjustmentFactors field", + inRequestExt: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: nil}}, + outBidAdjustmentFactors: nil, + }, + { + desc: "Non-nil request ext, valid BidAdjustmentFactors field", + inRequestExt: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}}}, + outBidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}, + }, + } + for _, test := range testCases { + actualBidAdjustmentFactors := getExtBidAdjustmentFactors(test.inRequestExt) + + assert.Equal(t, test.outBidAdjustmentFactors, actualBidAdjustmentFactors, "%s. Unexpected BidAdjustmentFactors value. \n", test.desc) + } +} + func TestCleanOpenRTBRequestsLMT(t *testing.T) { var ( enabled int8 = 1 @@ -346,7 +646,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) @@ -427,7 +727,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig) result := results["appnexus"] assert.Nil(t, errs) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index acfd4a1e71f..23daaf0f76e 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -19,6 +19,7 @@ type ExtRequestPrebid struct { StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` Targeting *ExtRequestTargeting `json:"targeting,omitempty"` SupportDeals bool `json:"supportdeals,omitempty"` + Debug bool `json:"debug,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains From cc4350270973749b5cbcc4f5bd191f4daeb13dbe Mon Sep 17 00:00:00 2001 From: Adprime <64427228+Adprime@users.noreply.github.com> Date: Fri, 7 Aug 2020 18:06:46 +0300 Subject: [PATCH 159/603] Remove redundad struct (#1432) --- adapters/adprime/adprime.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 007d3c86570..8594cb5d2e4 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -24,10 +24,6 @@ func NewAdprimeBidder(endpoint string) *AdprimeAdapter { } } -type adprimeParams struct { - TagID string `json:"TagID"` -} - // MakeRequests create bid request for adprime demand func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error From e67dfa4b8b58f995bda215299e1a4435a3d0e59b Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 12 Aug 2020 10:14:36 -0400 Subject: [PATCH 160/603] Tcf2 id support (#1420) --- endpoints/auction_test.go | 5 +++-- endpoints/cookie_sync_test.go | 4 ++-- endpoints/setuid_test.go | 4 ++-- exchange/utils.go | 4 +++- exchange/utils_test.go | 4 ++-- gdpr/gdpr.go | 2 +- gdpr/impl.go | 35 ++++++++++++++++++------------ gdpr/impl_test.go | 35 +++++++++++++++++++++++------- privacy/enforcement.go | 5 +++-- privacy/enforcement_test.go | 40 +++++++++++++++++++++++++++++++---- 10 files changed, 100 insertions(+), 38 deletions(-) diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 5e9e9639a9c..028f119640a 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -408,6 +408,7 @@ type auctionMockPermissions struct { allowHostCookies bool allowPI bool allowGeo bool + allowID bool } func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { @@ -418,8 +419,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - return m.allowPI, m.allowGeo, nil +func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return m.allowPI, m.allowGeo, m.allowID, nil } func (m *auctionMockPermissions) AMPException() bool { diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 824e32f1957..f7974d2bc77 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -254,8 +254,8 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - return true, true, nil +func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return true, true, true, nil } func (g *gdprPerms) AMPException() bool { diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 3f47b257d2e..e63944e2aec 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -437,8 +437,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - return g.allowPI, g.allowPI, nil +func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return g.allowPI, g.allowPI, g.allowPI, nil } func (g *mockPermsSetUID) AMPException() bool { diff --git a/exchange/utils.go b/exchange/utils.go index 97fae7b78ca..2131aac5f41 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -107,12 +107,14 @@ func cleanOpenRTBRequests(ctx context.Context, coreBidder := resolveBidder(bidder.String(), aliases) var publisherID = labels.PubID - ok, geo, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) + ok, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) privacyEnforcement.GDPR = !ok && err == nil privacyEnforcement.GDPRGeo = !geo && err == nil + privacyEnforcement.GDPRID = !id && err == nil } else { privacyEnforcement.GDPR = false privacyEnforcement.GDPRGeo = false + privacyEnforcement.GDPRID = false } privacyEnforcement.Apply(bidReq, ampGDPRException) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 3b919d3da56..528e875ab16 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -28,8 +28,8 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - return p.personalInfoAllowed, p.personalInfoAllowed, nil +func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, nil } func (p *permissionsMock) AMPException() bool { diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 0dfa12f5ebd..04db8cb92ed 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -23,7 +23,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) + PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) // Exposes the AMP execption flag AMPException() bool diff --git a/gdpr/impl.go b/gdpr/impl.go index 60db804aec6..2deddc7b2ba 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -42,10 +42,10 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { +func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { _, ok := p.cfg.NonStandardPublisherMap[PublisherID] if ok { - return true, true, nil + return true, true, true, nil } id, ok := p.vendorIDs[bidder] @@ -54,10 +54,10 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt } if consent == "" { - return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil + return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil } - return false, false, nil + return false, false, false, nil } func (p *permissionsImpl) AMPException() bool { @@ -98,19 +98,19 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, error) { +func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, bool, error) { // If we're not given a consent string, respect the preferences in the app config. if consent == "" { - return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil + return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil } parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { - return false, false, err + return false, false, false, err } if vendor == nil { - return false, false, nil + return false, false, false, nil } if parsedConsent.Version() == 2 { @@ -118,21 +118,22 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent return p.allowPITCF2(parsedConsent, vendor, vendorID) } if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) { - return true, true, nil + return true, true, true, nil } } else { if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, true, nil + return true, true, true, nil } } - return false, false, nil + return false, false, false, nil } -func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, err error) { +func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, allowID bool, err error) { consent, ok := parsedConsent.(tcf2.ConsentMetadata) err = nil allowPI = false allowGeo = false + allowID = false if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return @@ -142,6 +143,12 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a } else { allowGeo = true } + for i := 2; i <= 10; i++ { + if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i)) { + allowID = true + break + } + } // Set to true so any purpose check can flip it to false allowPI = true if p.cfg.TCF2.Purpose1.Enabled { @@ -214,8 +221,8 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) { - return true, true, nil +func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return true, true, true, nil } func (a AlwaysAllow) AMPException() bool { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 05b2fb6d98e..053e87536ab 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -207,17 +207,17 @@ func TestAllowPersonalInfo(t *testing.T) { } // PI needs both purposes to succeed - allowPI, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, false, allowPI) - allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, true, allowPI) // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1} - allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, _, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, true, allowPI) } @@ -257,6 +257,7 @@ type tcf2TestDef struct { consent string allowPI bool allowGeo bool + allowID bool } func TestAllowPersonalInfoTCF2(t *testing.T) { @@ -285,6 +286,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", allowPI: false, allowGeo: false, + allowID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", @@ -292,6 +294,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", allowPI: true, allowGeo: true, + allowID: true, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", @@ -299,14 +302,16 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", allowPI: true, allowGeo: false, + allowID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + assert.EqualValuesf(t, td.allowID, allowID, "AllowGeo failure on %s", td.description) } } @@ -328,10 +333,11 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1} - allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") assert.EqualValuesf(t, true, allowPI, "AllowPI failure") assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") + assert.EqualValuesf(t, true, allowID, "AllowID failure") } @@ -361,6 +367,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", allowPI: false, allowGeo: false, + allowID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", @@ -368,6 +375,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", allowPI: false, allowGeo: false, + allowID: false, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", @@ -375,14 +383,16 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", allowPI: false, allowGeo: false, + allowID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + assert.EqualValuesf(t, td.allowID, allowID, "AllowPI failure on %s", td.description) } } @@ -413,6 +423,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: false, allowGeo: false, + allowID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", @@ -420,6 +431,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: true, allowGeo: true, + allowID: true, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", @@ -427,14 +439,16 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: true, allowGeo: false, + allowID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) } } @@ -458,6 +472,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set + // Purpose one treatment will fail PI, but allow passing the IDs. testDefs := []tcf2TestDef{ { description: "Appnexus vendor test, insufficient purposes claimed", @@ -465,6 +480,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: false, allowGeo: false, + allowID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", @@ -472,6 +488,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: false, allowGeo: true, + allowID: true, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", @@ -479,14 +496,16 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", allowPI: false, allowGeo: false, + allowID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) + assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) } } diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 8a5d201fc95..9c23c320680 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -10,12 +10,13 @@ type Enforcement struct { COPPA bool GDPR bool GDPRGeo bool + GDPRID bool LMT bool } // Any returns true if at least one privacy policy requires enforcement. func (e Enforcement) Any() bool { - return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.LMT + return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.GDPRID || e.LMT } // Apply cleans personally identifiable information from an OpenRTB bid request. @@ -64,7 +65,7 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs } // If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above) - if e.CCPA || e.GDPR || e.LMT { + if e.CCPA || e.GDPRID || e.LMT { return ScrubStrategyUserID } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 968c6354710..ef02e28147a 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -21,6 +21,7 @@ func TestAny(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: false, }, expected: false, @@ -32,6 +33,7 @@ func TestAny(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + GDPRID: true, LMT: true, }, expected: true, @@ -43,6 +45,7 @@ func TestAny(t *testing.T) { COPPA: true, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: true, }, expected: true, @@ -72,6 +75,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + GDPRID: true, LMT: true, }, ampGDPRException: false, @@ -87,6 +91,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: false, }, ampGDPRException: false, @@ -102,6 +107,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: false, }, ampGDPRException: false, @@ -117,6 +123,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: true, + GDPRID: true, LMT: false, }, ampGDPRException: false, @@ -132,6 +139,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: true, + GDPRID: true, LMT: false, }, ampGDPRException: true, @@ -147,6 +155,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: false, }, ampGDPRException: true, @@ -162,6 +171,7 @@ func TestApply(t *testing.T) { COPPA: true, GDPR: true, GDPRGeo: true, + GDPRID: true, LMT: false, }, ampGDPRException: true, @@ -177,6 +187,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: true, GDPRGeo: false, + GDPRID: true, LMT: false, }, ampGDPRException: false, @@ -192,6 +203,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: true, + GDPRID: false, LMT: false, }, ampGDPRException: false, @@ -200,6 +212,22 @@ func TestApply(t *testing.T) { expectedUser: ScrubStrategyUserNone, expectedUserGeo: ScrubStrategyGeoReducedPrecision, }, + { + description: "GDPR Only, ID exception", + enforcement: Enforcement{ + CCPA: false, + COPPA: false, + GDPR: true, + GDPRGeo: true, + GDPRID: false, + LMT: false, + }, + ampGDPRException: false, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedUser: ScrubStrategyUserNone, + expectedUserGeo: ScrubStrategyGeoReducedPrecision, + }, { description: "LMT Only", enforcement: Enforcement{ @@ -207,6 +235,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: true, }, ampGDPRException: false, @@ -222,6 +251,7 @@ func TestApply(t *testing.T) { COPPA: false, GDPR: false, GDPRGeo: false, + GDPRID: false, LMT: true, }, ampGDPRException: true, @@ -258,10 +288,12 @@ func TestApplyNoneApplicable(t *testing.T) { m := &mockScrubber{} enforcement := Enforcement{ - CCPA: false, - COPPA: false, - GDPR: false, - LMT: false, + CCPA: false, + COPPA: false, + GDPR: false, + GDPRGeo: false, + GDPRID: false, + LMT: false, } enforcement.apply(req, false, m) From 549cc791f7d06d1a3dfaa1be061b1753c4a8146a Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 12 Aug 2020 12:27:57 -0400 Subject: [PATCH 161/603] Default TCF1 GVL in anticipation of IAB no longer hosting the v1 GVL (#1433) --- config/config.go | 9 +++ gdpr/vendorlist-fetching.go | 33 ++++++++++- gdpr/vendorlist-fetching_test.go | 97 ++++++++++++++++++++++++++++++++ static/tcf1/fallback_gvl.json | 1 + 4 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 static/tcf1/fallback_gvl.json diff --git a/config/config.go b/config/config.go index 9663b021b5b..7fc77855810 100755 --- a/config/config.go +++ b/config/config.go @@ -156,6 +156,7 @@ type GDPR struct { Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]int + TCF1 TCF1 `mapstructure:"tcf1"` TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` } @@ -180,6 +181,12 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } +// TCF1 defines the TCF1 specific configurations for GDPR +type TCF1 struct { + FetchGVL bool `mapstructure:"fetch_gvl"` + FallbackGVLPath string `mapstructure:"fallback_gvl_path"` +} + // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { Enabled bool `mapstructure:"enabled"` @@ -885,6 +892,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) + v.SetDefault("gdpr.tcf1.fetch_gvl", true) + v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json") v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) v.SetDefault("gdpr.tcf2.purpose2.enabled", true) diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 987622a6a8a..a0a73c93008 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -27,8 +27,20 @@ type saveVendors func(uint16, api.VendorList) // Nothing in this file is exported. Public APIs can be found in gdpr.go func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { + var fallbackVL api.VendorList = nil + + if TCFVer == tCF1 && len(cfg.TCF1.FallbackGVLPath) > 0 { + fallbackVL = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) + } + + // If we are not going to try fetching the GVL dynamically, we have a simple fetcher + if !cfg.TCF1.FetchGVL && TCFVer == tCF1 && fallbackVL != nil { + return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { + return fallbackVL, nil + } + } // These save and load functions can be used to store & retrieve lists from our cache. - save, load := newVendorListCache() + save, load := newVendorListCache(fallbackVL) withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) defer cancel() @@ -46,6 +58,9 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http if list != nil { return list, nil } + if fallbackVL != nil { + return fallbackVL, nil + } return nil, fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", id) } } @@ -132,7 +147,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return newList.Version() } -func newVendorListCache() (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) { +func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) { cache := &sync.Map{} save = func(id uint16, list api.VendorList) { @@ -143,7 +158,19 @@ func newVendorListCache() (save func(id uint16, list api.VendorList), load func( if ok { return list.(vendorlist.VendorList) } - return nil + return fallbackVL } return } + +func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList { + fallbackVLbody, err := ioutil.ReadFile(fallbackGVLPath) + if err != nil { + glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) + } + fallbackVL, err := vendorlist.ParseEagerly(fallbackVLbody) + if err != nil { + glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) + } + return fallbackVL +} diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 824f9178faa..c989ef4cef8 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/config" ) @@ -139,6 +141,101 @@ func TestVendorListMaker(t *testing.T) { assertStringsEqual(t, "https://vendorlist.consensu.org/v2/archives/vendor-list-v7.json", vendorListURLMaker(7, 2)) } +func TestDefaultVendorList(t *testing.T) { + firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ + 32: { + purposes: []int{1, 2}, + }, + }) + secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ + 12: { + purposes: []int{2}, + }, + }) + server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ + 1: firstVendorList, + 2: secondVendorList, + }))) + defer server.Close() + + testcfg := testConfig() + testcfg.TCF1.FetchGVL = true + testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" + fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) + + list, err := fetcher(context.Background(), 12) + assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) + assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version()) + + // Testing that we got the default vendorlist data, and not the version off the server. + vendor := list.Vendor(12) + assert.Equal(t, true, vendor.Purpose(1)) + assert.Equal(t, false, vendor.Purpose(2)) +} + +func TestDefaultVendorListPassthrough(t *testing.T) { + firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ + 32: { + purposes: []int{1, 2}, + }, + }) + secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ + 12: { + purposes: []int{2}, + }, + }) + server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ + 1: firstVendorList, + 2: secondVendorList, + }))) + defer server.Close() + + testcfg := testConfig() + testcfg.TCF1.FetchGVL = true + testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" + fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) + list, err := fetcher(context.Background(), 2) + assert.NoError(t, err, "Error with fetching existing vendorlist: %v", err) + assert.Equal(t, uint16(2), list.Version(), "Expected to fetch mock list version 2, got version %d", list.Version()) + + // Testing that we got the testing vendorlist data, and not the default. + vendor := list.Vendor(12) + assert.Equal(t, false, vendor.Purpose(1)) + assert.Equal(t, true, vendor.Purpose(2)) +} + +func TestDefaultVendorListNoFetch(t *testing.T) { + firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ + 32: { + purposes: []int{1, 2}, + }, + }) + secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ + 12: { + purposes: []int{2}, + }, + }) + server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ + 1: firstVendorList, + 2: secondVendorList, + }))) + defer server.Close() + + testcfg := testConfig() + testcfg.TCF1.FetchGVL = false + testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" + fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) + list, err := fetcher(context.Background(), 2) + assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) + assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version()) + + // Testing that we got the default vendorlist data, and not the version off the server. + vendor := list.Vendor(12) + assert.Equal(t, true, vendor.Purpose(1)) + assert.Equal(t, false, vendor.Purpose(2)) + +} + // mockServer returns a handler which returns the given response for each global vendor list version. // The latestVersion param can be used to mock "updates" which occur after PBS has been turned on. // For example, if latestVersion is 3, but the responses map has data at "4", the server will return diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json new file mode 100644 index 00000000000..86895a52362 --- /dev/null +++ b/static/tcf1/fallback_gvl.json @@ -0,0 +1 @@ +{"vendorListVersion":214,"lastUpdated":"2020-08-06T16:00:35Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":360,"name":"Permutive Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy","deletedDate":"2020-03-31T00:00:00Z"},{"id":361,"name":"Permutive","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":384,"name":"Pixalate, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://pixalate.com/privacypolicy/","deletedDate":"2019-11-08T00:00:00Z"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Dr. Banner","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":612,"name":"Adnami Aps","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adnami.io/privacy","deletedDate":"2020-03-17T00:00:00Z"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"}]} \ No newline at end of file From b5f89336da09398de6d723c026825ad4bcf1afba Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 12 Aug 2020 12:37:43 -0400 Subject: [PATCH 162/603] update to the latest go-gdpr release (#1436) --- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a5b5a161cf4..108e383e743 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pelletier/go-toml v1.2.0 // indirect - github.com/prebid/go-gdpr v0.8.2 + github.com/prebid/go-gdpr v0.8.3 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 diff --git a/go.sum b/go.sum index 1ddab71332a..6da3f8898ba 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= @@ -77,8 +78,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prebid/go-gdpr v0.8.2 h1:mN2jKYZZpJkCYFQB/nDTJoPpuGYblOYP2UUzOzRggII= -github.com/prebid/go-gdpr v0.8.2/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= +github.com/prebid/go-gdpr v0.8.3 h1:rjCZNV0AdKygiGHpVhNB42usjEpTN3qidXUPB1yarb0= +github.com/prebid/go-gdpr v0.8.3/go.mod h1:TGzgqQDGKOVUkbqmY25K4uvcwMywSddXEaY4zUFiVBQ= github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= From 48c865cb0bc7596b6fdb001f4b992b519fa45575 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Wed, 12 Aug 2020 10:07:58 -0700 Subject: [PATCH 163/603] Video endpoint bid selection enhancements (#1419) Co-authored-by: Veronika Solovei --- endpoints/openrtb2/video_auction.go | 18 +++- endpoints/openrtb2/video_auction_test.go | 29 +++-- exchange/auction.go | 2 +- .../impcustomcachekeytest/multiImpVideo.json | 101 ++++++++++++++++++ .../multiImpVideoNoIncludeBidderKeys.json | 86 +++++++++++++++ exchange/targeting.go | 4 +- exchange/targeting_test.go | 6 +- 7 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 exchange/impcustomcachekeytest/multiImpVideo.json create mode 100644 exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 18678be541c..49ba287610b 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -470,7 +470,7 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError) if err := json.Unmarshal(bid.Ext, &tempRespBidExt); err != nil { return nil, err } - if tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbVastCacheKey)] == "" { + if tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)] == "" { continue } @@ -479,9 +479,9 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError) podId, _ := strconv.ParseInt(podNum, 0, 64) videoTargeting := openrtb_ext.VideoTargeting{ - HbPb: tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbpbConstantKey)], - HbPbCatDur: tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbCategoryDurationKey)], - HbCacheID: tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbVastCacheKey)], + HbPb: tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbpbConstantKey, seatBid.Seat)], + HbPbCatDur: tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbCategoryDurationKey, seatBid.Seat)], + HbCacheID: tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)], } adPod := findAdPod(podId, adPods) @@ -519,6 +519,14 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError) return &openrtb_ext.BidResponseVideo{AdPods: adPods}, nil } +func formatTargetingKey(key openrtb_ext.TargetingKey, bidderName string) string { + fullKey := fmt.Sprintf("%s_%s", string(key), bidderName) + if len(fullKey) > exchange.MaxKeyLength { + return string(fullKey[0:exchange.MaxKeyLength]) + } + return fullKey +} + func findAdPod(podInd int64, pods []*openrtb_ext.AdPod) *openrtb_ext.AdPod { for _, pod := range pods { if pod.PodId == podInd { @@ -623,9 +631,9 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro targeting := openrtb_ext.ExtRequestTargeting{ PriceGranularity: priceGranularity, - IncludeWinners: true, IncludeBrandCategory: inclBrandCat, DurationRangeSec: durationRangeSec, + IncludeBidderKeys: true, } vastXml := openrtb_ext.ExtRequestPrebidCacheVAST{} diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index f29ac3bfed9..b15c6a7b47a 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -81,6 +81,10 @@ func TestVideoEndpointImpressionsDuration(t *testing.T) { t.Fatalf("The request never made it into the Exchange.") } + var extData openrtb_ext.ExtRequest + json.Unmarshal(ex.lastRequest.Ext, &extData) + assert.True(t, extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") + assert.Len(t, ex.lastRequest.Imp, 22, "Incorrect number of impressions in request") assert.Equal(t, ex.lastRequest.Imp[0].ID, "1_0", "Incorrect impression id in request") assert.Equal(t, ex.lastRequest.Imp[0].Video.MaxDuration, int64(15), "Incorrect impression max duration in request") @@ -643,9 +647,9 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { bid2 := openrtb.Bid{} bid3 := openrtb.Bid{} - extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) - extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) - extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_406_30s","hb_size":"1x1"}}}`) + extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) + extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) + extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_406_30s","hb_size":"1x1"}}}`) bid1.Ext = extBid1 bids = append(bids, bid1) @@ -657,6 +661,7 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { bids = append(bids, bid3) seatBid.Bid = bids + seatBid.Seat = "appnexus" seatBids = append(seatBids, seatBid) openRtbBidResp.SeatBid = seatBids @@ -713,8 +718,8 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { bid1 := openrtb.Bid{} bid2 := openrtb.Bid{} - extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) - extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) + extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) + extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) bid1.Ext = extBid1 bids = append(bids, bid1) @@ -723,6 +728,7 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { bids = append(bids, bid2) seatBid.Bid = bids + seatBid.Seat = "appnexus" seatBids = append(seatBids, seatBid) openRtbBidResp.SeatBid = seatBids @@ -1107,6 +1113,16 @@ func TestCCPA(t *testing.T) { } } +func TestFormatTargetingKey(t *testing.T) { + res := formatTargetingKey(openrtb_ext.HbCategoryDurationKey, "appnexus") + assert.Equal(t, "hb_pb_cat_dur_appnex", res, "Tergeting key constructed incorrectly") +} + +func TestFormatTargetingKeyLongKey(t *testing.T) { + res := formatTargetingKey(openrtb_ext.HbpbConstantKey, "20.00") + assert.Equal(t, "hb_pb_20.00", res, "Tergeting key constructed incorrectly") +} + func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) mockModule := &mockAnalyticsModule{} @@ -1205,9 +1221,10 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb if debugLog != nil && debugLog.Enabled { m.cache.called = true } - ext := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"20.00","hb_pb_cat_dur":"20.00_395_30s","hb_size":"1x1", "hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) + ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) return &openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{{ + Seat: "appnexus", Bid: []openrtb.Bid{ {ID: "01", ImpID: "1_0", Ext: ext}, {ID: "02", ImpID: "1_1", Ext: ext}, diff --git a/exchange/auction.go b/exchange/auction.go index dcb73708df7..45e1422540e 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -130,7 +130,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, var customCacheKey string var catDur string useCustomCacheKey := false - if competitiveExclusion && isOverallWinner { + if competitiveExclusion && isOverallWinner || includeBidderKeys { // set custom cache key for winning bid when competitive exclusion applies catDur = bidCategory[topBidPerBidder.bid.ID] if len(catDur) > 0 { diff --git a/exchange/impcustomcachekeytest/multiImpVideo.json b/exchange/impcustomcachekeytest/multiImpVideo.json new file mode 100644 index 00000000000..7c1d7af02ea --- /dev/null +++ b/exchange/impcustomcachekeytest/multiImpVideo.json @@ -0,0 +1,101 @@ +{ + "bidRequest": { + "imp": [{ + "id": "1_0" + }, + { + "id": "1_1" + } + ] + }, + "pbsBids": [{ + "bid": { + "id": "apn_1_0", + "impid": "1_0", + "price": 12.00, + "nurl": "http://apn_1_0.com", + "cat": ["12.00_sports_30s"] + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "spotx_1_0", + "impid": "1_0", + "price": 20.00, + "nurl": "http://spotx_1_0.com", + "cat": ["20_news_30s"] + }, + "bidType": "video", + "bidder": "spotx" + }, { + "bid": { + "id": "apn_1_1", + "impid": "1_1", + "price": 18.00, + "nurl": "http://apn_1_1.com", + "cat": ["18_furniture_30s"] + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "spotx_1_1", + "impid": "1_1", + "price": 17.00, + "nurl": "http://spotx_1_1.com", + "cat": ["17_auto_30s"] + }, + "bidType": "video", + "bidder": "spotx" + }, { + "bid": { + "id": "rubicon_1_1", + "impid": "1_1", + "price": 17.50, + "nurl": "http://rubicon_1_1.com", + "cat": ["17_music_30s"] + }, + "bidType": "video", + "bidder": "rubicon" + }], + "expectedCacheables": [{ + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "12.00_sports_30s" + }, { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "20_news_30s" + }, { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "18_furniture_30s" + }, + { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "17_auto_30s" + }, + { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://rubicon_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "17_music_30s" + } + ], + "defaultTTLs": { + "banner": 300, + "video": 3600, + "audio": 1800, + "native": 300 + }, + "targetDataIncludeWinners": false, + "targetDataIncludeBidderKeys": true, + "targetDataIncludeCacheBids": false, + "targetDataIncludeCacheVast": true +} diff --git a/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json b/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json new file mode 100644 index 00000000000..0b8ee1415e3 --- /dev/null +++ b/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json @@ -0,0 +1,86 @@ +{ + "bidRequest": { + "imp": [{ + "id": "1_0" + }, + { + "id": "1_1" + } + ] + }, + "pbsBids": [{ + "bid": { + "id": "apn_1_0", + "impid": "1_0", + "price": 12.00, + "nurl": "http://apn_1_0.com", + "cat": ["12.00_sports_30s"] + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "spotx_1_0", + "impid": "1_0", + "price": 20.00, + "nurl": "http://spotx_1_0.com", + "cat": ["20_news_30s"] + }, + "bidType": "video", + "bidder": "spotx" + }, { + "bid": { + "id": "apn_1_1", + "impid": "1_1", + "price": 18.00, + "nurl": "http://apn_1_1.com", + "cat": ["18_furniture_30s"] + }, + "bidType": "video", + "bidder": "appnexus" + }, { + "bid": { + "id": "spotx_1_1", + "impid": "1_1", + "price": 17.00, + "nurl": "http://spotx_1_1.com", + "cat": ["17_auto_30s"] + }, + "bidType": "video", + "bidder": "spotx" + }, { + "bid": { + "id": "rubicon_1_1", + "impid": "1_1", + "price": 17.50, + "nurl": "http://rubicon_1_1.com", + "cat": ["17_music_30s"] + }, + "bidType": "video", + "bidder": "rubicon" + }], + "expectedCacheables": [ + { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "20_news_30s" + }, + { + "Type": "xml", + "TTLSeconds": 3660, + "Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e", + "Key": "18_furniture_30s" + } + ], + "defaultTTLs": { + "banner": 300, + "video": 3600, + "audio": 1800, + "native": 300 + }, + "targetDataIncludeWinners": true, + "targetDataIncludeBidderKeys": false, + "targetDataIncludeCacheBids": false, + "targetDataIncludeCacheVast": true +} diff --git a/exchange/targeting.go b/exchange/targeting.go index dca57653b44..47bfeb655fe 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -7,7 +7,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const maxKeyLength = 20 +const MaxKeyLength = 20 // targetData tracks information about the winning Bid in each Imp. // @@ -83,7 +83,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool) { if targData.includeBidderKeys { - keys[key.BidderKey(bidderName, maxKeyLength)] = value + keys[key.BidderKey(bidderName, MaxKeyLength)] = value } if targData.includeWinners && overallWinner { keys[string(key)] = value diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 11b60db01f3..284d56be42e 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -50,13 +50,13 @@ func TestTargetingCache(t *testing.T) { // Make sure that the cache keys exist on the bids where they're expected to assertKeyExists(t, bids["winning-bid"], string(openrtb_ext.HbCacheKey), true) - assertKeyExists(t, bids["winning-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, maxKeyLength), true) + assertKeyExists(t, bids["winning-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), true) assertKeyExists(t, bids["contending-bid"], string(openrtb_ext.HbCacheKey), false) - assertKeyExists(t, bids["contending-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderRubicon, maxKeyLength), true) + assertKeyExists(t, bids["contending-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderRubicon, MaxKeyLength), true) assertKeyExists(t, bids["losing-bid"], string(openrtb_ext.HbCacheKey), false) - assertKeyExists(t, bids["losing-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, maxKeyLength), false) + assertKeyExists(t, bids["losing-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), false) //assert hb_cache_host was included assert.Contains(t, string(bids["winning-bid"].Ext), string(openrtb_ext.HbConstantCacheHostKey)) From cce496720f13d30d45154ca7d80e81884fb9a1ac Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Wed, 12 Aug 2020 10:19:18 -0700 Subject: [PATCH 164/603] [WIP] Bid deduplication enhancement (#1430) Co-authored-by: Veronika Solovei --- exchange/exchange.go | 29 ++++++++++-- exchange/exchange_test.go | 96 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 9 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index ad591f57794..57e13644163 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -10,6 +10,7 @@ import ( "net/http" "runtime/debug" "sort" + "strconv" "strings" "time" @@ -484,6 +485,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques bidderName openrtb_ext.BidderName bidIndex int bidID string + bidPrice string } dedupe := make(map[string]bidDedupe) @@ -580,15 +582,34 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques } var categoryDuration string + var dupeKey string if brandCatExt.WithCategory { categoryDuration = fmt.Sprintf("%s_%s_%ds", pb, category, newDur) + dupeKey = category } else { categoryDuration = fmt.Sprintf("%s_%ds", pb, newDur) + dupeKey = categoryDuration } - if dupe, ok := dedupe[categoryDuration]; ok { - // 50% chance for either bid with duplicate categoryDuration values to be kept - if rand.Intn(100) < 50 { + if dupe, ok := dedupe[dupeKey]; ok { + + dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) + if err != nil { + dupeBidPrice = 0 + } + currBidPrice, err := strconv.ParseFloat(pb, 64) + if err != nil { + currBidPrice = 0 + } + if dupeBidPrice == currBidPrice { + if rand.Intn(100) < 50 { + dupeBidPrice = -1 + } else { + currBidPrice = -1 + } + } + + if dupeBidPrice < currBidPrice { if dupe.bidderName == bidderName { // An older bid from the current bidder bidsToRemove = append(bidsToRemove, dupe.bidIndex) @@ -612,7 +633,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques } } res[bidID] = categoryDuration - dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID} + dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } if len(bidsToRemove) > 0 { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7da7b62e70b..5fbdb1c57a9 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1352,19 +1352,22 @@ func TestCategoryDedupe(t *testing.T) { cats4 := []string{"IAB1-2000"} bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} + bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, 0} bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} selectedBids := make(map[string]int) expectedCategories := map[string]string{ "bid_id1": "10.00_Electronics_30s", "bid_id2": "14.00_Sports_50s", - "bid_id3": "10.00_Electronics_30s", + "bid_id3": "20.00_Electronics_30s", + "bid_id5": "20.00_Electronics_30s", } numIterations := 10 @@ -1378,6 +1381,7 @@ func TestCategoryDedupe(t *testing.T) { &bid1_2, &bid1_3, &bid1_4, + &bid1_5, } seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} @@ -1388,7 +1392,7 @@ func TestCategoryDedupe(t *testing.T) { bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") - assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") + assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected") assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match") @@ -1401,8 +1405,90 @@ func TestCategoryDedupe(t *testing.T) { } assert.Equal(t, numIterations, selectedBids["bid_id2"], "Bid 2 did not make it through every time") - assert.NotEqual(t, numIterations, selectedBids["bid_id1"], "Bid 1 made it through every time") - assert.NotEqual(t, numIterations, selectedBids["bid_id3"], "Bid 3 made it through every time") + assert.Equal(t, 0, selectedBids["bid_id1"], "Bid 1 should be rejected on every iteration due to lower price") + assert.NotEqual(t, 0, selectedBids["bid_id3"], "Bid 3 should be accepted at least once") + assert.NotEqual(t, 0, selectedBids["bid_id5"], "Bid 5 should be accepted at least once") +} + +func TestNoCategoryDedupe(t *testing.T) { + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + requestExt := newExtRequestNoBrandCat() + + targData := &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: true, + } + + adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + + cats1 := []string{"IAB1-3"} + cats2 := []string{"IAB1-4"} + cats4 := []string{"IAB1-2000"} + bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} + bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} + + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + + selectedBids := make(map[string]int) + expectedCategories := map[string]string{ + "bid_id1": "14.00_30s", + "bid_id2": "14.00_30s", + "bid_id3": "20.00_30s", + "bid_id4": "20.00_30s", + "bid_id5": "10.00_30s", + } + + numIterations := 10 + + // Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes + // and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but + // if you notice false fails from this test increase numIterations to make it even less likely to happen. + for i := 0; i < numIterations; i++ { + innerBids := []*pbsOrtbBid{ + &bid1_1, + &bid1_2, + &bid1_3, + &bid1_4, + &bid1_5, + } + + seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + bidderName1 := openrtb_ext.BidderName("appnexus") + + adapterBids[bidderName1] = &seatBid + + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + + assert.Equal(t, nil, err, "Category mapping error should be empty") + assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") + assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") + assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(3|4)\] reason: Bid was deduplicated`), rejections[1], "Rejection message did not match expected") + assert.Equal(t, 3, len(adapterBids[bidderName1].bids), "Bidders number doesn't match") + assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match") + + for bidId, bidCat := range bidCategory { + assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match") + selectedBids[bidId]++ + } + } + assert.Equal(t, numIterations, selectedBids["bid_id5"], "Bid 5 did not make it through every time") + assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 1 should be selected at least once") + assert.NotEqual(t, 0, selectedBids["bid_id2"], "Bid 2 should be selected at least once") + assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 3 should be selected at least once") + assert.NotEqual(t, 0, selectedBids["bid_id4"], "Bid 4 should be selected at least once") + } func TestBidRejectionErrors(t *testing.T) { From 346617b0d08623e7976f18a63db28d2f8bd8bd1c Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 12 Aug 2020 13:58:40 -0400 Subject: [PATCH 165/603] Refactor rate converter separating scheduler from converter logic to improve testability (#1394) --- currencies/converter_info.go | 15 +- currencies/rate_converter.go | 120 +--- currencies/rate_converter_test.go | 676 ++++++------------- currencies/rates_test.go | 1 + endpoints/currency_rates.go | 7 +- endpoints/currency_rates_test.go | 51 +- endpoints/openrtb2/auction_benchmark_test.go | 3 +- exchange/bidder_test.go | 18 +- exchange/exchange_test.go | 25 +- exchange/legacy_test.go | 12 +- exchange/targeting_test.go | 2 +- main.go | 9 +- router/admin.go | 5 +- util/task/ticker_task.go | 53 ++ util/task/ticker_task_test.go | 63 ++ util/timeutil/time.go | 16 + 16 files changed, 431 insertions(+), 645 deletions(-) create mode 100644 util/task/ticker_task.go create mode 100644 util/task/ticker_task_test.go create mode 100644 util/timeutil/time.go diff --git a/currencies/converter_info.go b/currencies/converter_info.go index 6f4fda64c09..abbcde29fbc 100644 --- a/currencies/converter_info.go +++ b/currencies/converter_info.go @@ -5,18 +5,16 @@ import "time" // ConverterInfo holds information about converter setup type ConverterInfo interface { Source() string - FetchingInterval() time.Duration LastUpdated() time.Time Rates() *map[string]map[string]float64 AdditionalInfo() interface{} } type converterInfo struct { - source string - fetchingInterval time.Duration - lastUpdated time.Time - rates *map[string]map[string]float64 - additionalInfo interface{} + source string + lastUpdated time.Time + rates *map[string]map[string]float64 + additionalInfo interface{} } // Source returns converter's URL source @@ -24,11 +22,6 @@ func (ci converterInfo) Source() string { return ci.source } -// FetchingInterval returns converter's fetching interval in nanoseconds -func (ci converterInfo) FetchingInterval() time.Duration { - return ci.fetchingInterval -} - // LastUpdated returns converter's last updated time func (ci converterInfo) LastUpdated() time.Time { return ci.lastUpdated diff --git a/currencies/rate_converter.go b/currencies/rate_converter.go index d22f347b17c..f9ae67436f9 100644 --- a/currencies/rate_converter.go +++ b/currencies/rate_converter.go @@ -2,83 +2,43 @@ package currencies import ( "encoding/json" + "fmt" "io/ioutil" "net/http" "sync/atomic" "time" "github.com/golang/glog" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/timeutil" ) // RateConverter holds the currencies conversion rates dictionary type RateConverter struct { httpClient httpClient - done chan bool - updateNotifier chan<- int - fetchingInterval time.Duration staleRatesThreshold time.Duration syncSourceURL string rates atomic.Value // Should only hold Rates struct lastUpdated atomic.Value // Should only hold time.Time constantRates Conversions + time timeutil.Time } // NewRateConverter returns a new RateConverter func NewRateConverter( httpClient httpClient, syncSourceURL string, - fetchingInterval time.Duration, staleRatesThreshold time.Duration, ) *RateConverter { - return NewRateConverterWithNotifier( - httpClient, - syncSourceURL, - fetchingInterval, - staleRatesThreshold, - nil, // no notifier channel specified, won't send any notifications - ) -} - -// NewRateConverterDefault returns a RateConverter with default values. -// By default there will be no currencies conversions done. -// `currencies.ConstantRate` will be used. -func NewRateConverterDefault() *RateConverter { - return NewRateConverter(&http.Client{}, "", time.Duration(0), time.Duration(0)) -} - -// NewRateConverterWithNotifier returns a new RateConverter -// it allow to pass an update chan in which the number of ticks will be passed after each tick -// allowing clients to listen on updates -// Do not use this method -func NewRateConverterWithNotifier( - httpClient httpClient, - syncSourceURL string, - fetchingInterval time.Duration, - staleRatesThreshold time.Duration, - updateNotifier chan<- int, -) *RateConverter { - rc := &RateConverter{ + return &RateConverter{ httpClient: httpClient, - done: make(chan bool), - updateNotifier: updateNotifier, - fetchingInterval: fetchingInterval, staleRatesThreshold: staleRatesThreshold, syncSourceURL: syncSourceURL, rates: atomic.Value{}, lastUpdated: atomic.Value{}, constantRates: NewConstantRates(), + time: &timeutil.RealTime{}, } - - // In case host do not want to support currency lookup - // we just stop here and do nothing - if rc.fetchingInterval == time.Duration(0) { - return rc - } - - rc.Update() // Make sure to populate data before to return the converter - go rc.startPeriodicFetching() // Start periodic ticking - - return rc } // fetch allows to retrieve the currencies rates from the syncSourceURL provided @@ -93,6 +53,11 @@ func (rc *RateConverter) fetch() (*Rates, error) { return nil, err } + if response.StatusCode >= 400 { + message := fmt.Sprintf("The currency rates request failed with status code %d", response.StatusCode) + return nil, &errortypes.BadServerResponse{Message: message} + } + defer response.Body.Close() bytesJSON, err := ioutil.ReadAll(response.Body) @@ -110,14 +75,14 @@ func (rc *RateConverter) fetch() (*Rates, error) { } // Update updates the internal currencies rates from remote sources -func (rc *RateConverter) Update() error { +func (rc *RateConverter) update() error { rates, err := rc.fetch() if err == nil { rc.rates.Store(rates) - rc.lastUpdated.Store(time.Now()) + rc.lastUpdated.Store(rc.time.Now()) } else { - if rc.CheckStaleRates() { - rc.ClearRates() + if rc.checkStaleRates() { + rc.clearRates() glog.Errorf("Error updating conversion rates, falling back to constant rates: %v", err) } else { glog.Errorf("Error updating conversion rates: %v", err) @@ -127,37 +92,8 @@ func (rc *RateConverter) Update() error { return err } -// startPeriodicFetching starts the periodic fetching at the given interval -// triggers a first fetch when called before the first tick happen in order to initialize currencies rates map -// returns a chan in which the number of data updates everytime a new update was done -func (rc *RateConverter) startPeriodicFetching() { - - ticker := time.NewTicker(rc.fetchingInterval) - updatesTicksCount := 0 - - for { - select { - case <-ticker.C: - // Retries are handled by clients directly. - rc.Update() - updatesTicksCount++ - if rc.updateNotifier != nil { - rc.updateNotifier <- updatesTicksCount - } - case <-rc.done: - if ticker != nil { - ticker.Stop() - ticker = nil - } - return - } - } -} - -// StopPeriodicFetching stops the periodic fetching while keeping the latest currencies rates map -func (rc *RateConverter) StopPeriodicFetching() { - rc.done <- true - close(rc.done) +func (rc *RateConverter) Run() error { + return rc.update() } // LastUpdated returns time when currencies rates were updated @@ -178,18 +114,19 @@ func (rc *RateConverter) Rates() Conversions { return rc.constantRates } -// ClearRates sets the rates to nil -func (rc *RateConverter) ClearRates() { +// clearRates sets the rates to nil +func (rc *RateConverter) clearRates() { // atomic.Value field rates must be of type *Rates so we cast nil to that type rc.rates.Store((*Rates)(nil)) } -// CheckStaleRates checks if loaded third party conversion rates are stale -func (rc *RateConverter) CheckStaleRates() bool { +// checkStaleRates checks if loaded third party conversion rates are stale +func (rc *RateConverter) checkStaleRates() bool { if rc.staleRatesThreshold <= 0 { return false } - currentTime := time.Now().UTC() + + currentTime := rc.time.Now().UTC() if lastUpdated := rc.lastUpdated.Load(); lastUpdated != nil { delta := currentTime.Sub(lastUpdated.(time.Time).UTC()) if delta.Seconds() > rc.staleRatesThreshold.Seconds() { @@ -202,14 +139,11 @@ func (rc *RateConverter) CheckStaleRates() bool { // GetInfo returns setup information about the converter func (rc *RateConverter) GetInfo() ConverterInfo { var rates *map[string]map[string]float64 - if rc.Rates() != nil { - rates = rc.Rates().GetRates() - } + rates = rc.Rates().GetRates() return converterInfo{ - source: rc.syncSourceURL, - fetchingInterval: rc.fetchingInterval, - lastUpdated: rc.LastUpdated(), - rates: rates, + source: rc.syncSourceURL, + lastUpdated: rc.LastUpdated(), + rates: rates, } } diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go index d717d1a3f9c..7beb9523d28 100644 --- a/currencies/rate_converter_test.go +++ b/currencies/rate_converter_test.go @@ -1,4 +1,4 @@ -package currencies_test +package currencies import ( "io/ioutil" @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/currencies" + "github.com/prebid/prebid-server/util/task" "github.com/stretchr/testify/assert" ) @@ -27,423 +27,148 @@ func getMockRates() []byte { }`) } -func TestFetch_Success(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - }), - ) - - defer mockedHttpServer.Close() - - expectedRates := ¤cies.Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), - Conversions: map[string]map[string]float64{ - "USD": { - "GBP": 0.77208, - }, - "GBP": { - "USD": 1.2952, - }, - }, - } - - // Execute: - beforeExecution := time.Now() - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) - assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() should return a time set") - assert.True(t, currencyConverter.LastUpdated().After(beforeExecution), "LastUpdated() should be after last update") - rates := currencyConverter.Rates() - assert.NotNil(t, rates, "Rates() should not return nil") - assert.Equal(t, expectedRates, rates, "Rates() doesn't return expected rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") -} - -func TestFetch_Fail404(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusNotFound) - }), - ) - - defer mockedHttpServer.Close() - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) - assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") +// FakeTime implements the Time interface +type FakeTime struct { + time time.Time } -func TestFetch_FailErrorHttpClient(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusBadRequest) - }), - ) - - defer mockedHttpServer.Close() - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) - assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") +func (mc *FakeTime) Now() time.Time { + return mc.time } -func TestFetch_FailBadSyncURL(t *testing.T) { - - // Setup: - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - "justaweirdurl", - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") -} - -func TestFetch_FailBadJSON(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusOK) - rw.Write([]byte( - `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - }, - "badJsonHere" - } - }`, - )) - }), - ) - - defer mockedHttpServer.Close() - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) - assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") -} - -func TestFetch_InvalidRemoteResponseContent(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusOK) - rw.Write(nil) - }), - ) - - defer mockedHttpServer.Close() - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(24)*time.Hour, - time.Duration(24)*time.Hour, - ) - - // Verify: - assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs)) - assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") -} - -func TestInit(t *testing.T) { - - // Setup: - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - }), - ) - - // Execute: - expectedTicks := 5 - ticksTimes := []time.Time{} - ticks := make(chan int) - currencyConverter := currencies.NewRateConverterWithNotifier( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(24)*time.Hour, - ticks, - ) +func TestReadWriteRates(t *testing.T) { + // Setup + mockServerHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(mockStatus) + rw.Write([]byte(mockResponse)) + }) + } - // Verify: - expectedIntervalDuration := time.Duration(100) * time.Millisecond - errorMargin := 0.1 // 10% error margin - expectedRates := ¤cies.Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), - Conversions: map[string]map[string]float64{ - "USD": { - "GBP": 0.77208, - }, - "GBP": { - "USD": 1.2952, - }, + tests := []struct { + description string + giveFakeTime time.Time + giveMockUrl string + giveMockResponse []byte + giveMockStatus int + wantUpdateErr bool + wantConstantRates bool + wantLastUpdated time.Time + wantDataAsOf time.Time + wantConversions map[string]map[string]float64 + }{ + { + description: "Fetching currency rates successfully", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockResponse: getMockRates(), + giveMockStatus: 200, + wantLastUpdated: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + wantDataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), + wantConversions: map[string]map[string]float64{"USD": {"GBP": 0.77208}, "GBP": {"USD": 1.2952}}, + }, + { + description: "Currency rates endpoint returns empty response", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockResponse: []byte("{}"), + giveMockStatus: 200, + wantLastUpdated: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + wantDataAsOf: time.Time{}, + wantConversions: nil, + }, + { + description: "Currency rates endpoint returns nil response", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockResponse: nil, + giveMockStatus: 200, + wantUpdateErr: true, + wantConstantRates: true, + wantLastUpdated: time.Time{}, + }, + { + description: "Currency rates endpoint returns non-2xx status code", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockResponse: []byte(`{"message": "Not Found"}`), + giveMockStatus: 404, + wantUpdateErr: true, + wantConstantRates: true, + wantLastUpdated: time.Time{}, + }, + { + description: "Currency rates endpoint returns invalid json response", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockResponse: []byte(`{"message": Invalid-JSON-No-Surrounding-Quotes}`), + giveMockStatus: 200, + wantUpdateErr: true, + wantConstantRates: true, + wantLastUpdated: time.Time{}, + }, + { + description: "Currency rates endpoint url is invalid", + giveFakeTime: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), + giveMockUrl: "invalidurl", + giveMockResponse: getMockRates(), + giveMockStatus: 200, + wantUpdateErr: true, + wantConstantRates: true, + wantLastUpdated: time.Time{}, }, } - // At each ticks, do couple checks - for ticksCount := range ticks { - ticksTimes = append(ticksTimes, time.Now()) - if len(ticksTimes) > 1 { - intervalDuration := ticksTimes[len(ticksTimes)-1].Truncate(time.Millisecond).Sub(ticksTimes[len(ticksTimes)-2].Truncate(time.Millisecond)) - intervalDiff := float64(float64(intervalDuration.Nanoseconds()) / float64(expectedIntervalDuration.Nanoseconds())) - assert.False(t, intervalDiff > float64(errorMargin*100), "Interval between ticks should be: %d but was: %d", expectedIntervalDuration, intervalDuration) - } - - assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated should be set") - assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") + for _, tt := range tests { + mockedHttpServer := httptest.NewServer(mockServerHandler(tt.giveMockResponse, tt.giveMockStatus)) + defer mockedHttpServer.Close() - if ticksCount == expectedTicks { - currencyConverter.StopPeriodicFetching() - return + var url string + if len(tt.giveMockUrl) > 0 { + url = tt.giveMockUrl + } else { + url = mockedHttpServer.URL } - } -} - -func TestStop(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - }), - ) - - // Execute: - expectedTicks := 2 - ticks := make(chan int) - currencyConverter := currencies.NewRateConverterWithNotifier( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(24)*time.Hour, - ticks, - ) - - // Let the currency converter fetch 5 times before stopping it - for ticksCount := range ticks { - if ticksCount == expectedTicks { - currencyConverter.StopPeriodicFetching() - break + currencyConverter := NewRateConverter( + &http.Client{}, + url, + 24*time.Hour, + ) + currencyConverter.time = &FakeTime{time: tt.giveFakeTime} + err := currencyConverter.Run() + + if tt.wantUpdateErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) } - } - lastFetched := time.Now() - - // Verify: - // Check for the next 1 second that no fetch was triggered - time.Sleep(1 * time.Second) - - assert.False(t, currencyConverter.LastUpdated().After(lastFetched), "LastUpdated() shouldn't be after `lastFetched` since the periodic fetching is stopped") -} - -func TestInitWithZeroDuration(t *testing.T) { - - // Setup: - calledURLs := []string{} - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - }), - ) - - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(0), - time.Duration(24)*time.Hour, - ) - - // Verify: - // Check for the next 1 second that no fetch was triggered - time.Sleep(1 * time.Second) - - assert.Equal(t, 0, len(calledURLs), "sync URL shouldn't have been called but was called %d times", 0, len(calledURLs)) - assert.Equal(t, (time.Time{}), currencyConverter.LastUpdated(), "LastUpdated() shouldn't be set") - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") - assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil") -} - -func TestRates(t *testing.T) { - - // Setup: - testCases := []struct { - from string - to string - expectedRate float64 - hasError bool - }{ - {from: "USD", to: "GBP", expectedRate: 0.77208, hasError: false}, - {from: "GBP", to: "USD", expectedRate: 1.2952, hasError: false}, - {from: "GBP", to: "EUR", expectedRate: 0, hasError: true}, - {from: "CNY", to: "EUR", expectedRate: 0, hasError: true}, - {from: "", to: "EUR", expectedRate: 0, hasError: true}, - {from: "CNY", to: "", expectedRate: 0, hasError: true}, - {from: "", to: "", expectedRate: 0, hasError: true}, - {from: "USD", to: "USD", expectedRate: 1, hasError: false}, - } - - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - }), - ) - - // Execute: - ticks := make(chan int) - currencyConverter := currencies.NewRateConverterWithNotifier( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(24)*time.Hour, - ticks, - ) - rates := currencyConverter.Rates() - // Let the currency converter ticks 1 time before to stop it - select { - case <-ticks: - currencyConverter.StopPeriodicFetching() - } - - // Verify: - assert.NotNil(t, rates, "rates shouldn't be nil") - for _, tc := range testCases { - rate, err := rates.GetRate(tc.from, tc.to) - - if tc.hasError { - assert.NotNil(t, err, "err shouldn't be nil") - assert.Equal(t, float64(0), rate, "rate should be 0") + if tt.wantConstantRates { + assert.Equal(t, currencyConverter.Rates(), &ConstantRates{}, tt.description) } else { - assert.Nil(t, err, "err should be nil") - assert.Equal(t, tc.expectedRate, rate, "rate doesn't match the expected one") + rates := currencyConverter.Rates().(*Rates) + assert.Equal(t, tt.wantConversions, (*rates).Conversions, tt.description) + assert.Equal(t, tt.wantDataAsOf, (*rates).DataAsOf, tt.description) } - } -} - -func TestRates_EmptyRates(t *testing.T) { - // Setup: - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write([]byte("")) - }), - ) - - // Execute: - // Will try to fetch directly on method call but will fail - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(24)*time.Hour, - ) - defer currencyConverter.StopPeriodicFetching() - - // Verify: - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + lastUpdated := currencyConverter.LastUpdated() + assert.Equal(t, tt.wantLastUpdated, lastUpdated, tt.description) + } } -func TestSelectRatesBasedOnStaleness(t *testing.T) { - calledURLs := []string{} - callCnt := 0 +func TestRateStaleness(t *testing.T) { + callCount := 0 mockedHttpServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - calledURLs = append(calledURLs, req.RequestURI) - if callCnt == 0 || callCnt >= 5 { + callCount++ + if callCount == 2 || callCount >= 5 { rw.WriteHeader(http.StatusOK) rw.Write([]byte(getMockRates())) } else { rw.WriteHeader(http.StatusNotFound) + rw.Write([]byte(`{"message": "Not Found"}`)) } - callCnt++ }), ) defer mockedHttpServer.Close() - expectedRates := ¤cies.Rates{ + expectedRates := &Rates{ DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), Conversions: map[string]map[string]float64{ "USD": { @@ -455,97 +180,83 @@ func TestSelectRatesBasedOnStaleness(t *testing.T) { }, } + initialFakeTime := time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC) + fakeTime := &FakeTime{time: initialFakeTime} + // Execute: - currencyConverter := currencies.NewRateConverter( + currencyConverter := NewRateConverter( &http.Client{}, mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(200)*time.Millisecond, + 30*time.Second, // stale rates threshold ) + currencyConverter.time = fakeTime - // Verify: - // Rates are valid at t=0, then invalid for 500ms before being valid again - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + // First Update call results in error + err1 := currencyConverter.Run() + assert.NotNil(t, err1) - time.Sleep(100 * time.Millisecond) - // Rates have been invalid for ~100ms, rates not stale yet - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + // Verify constant rates are used and last update ts is not set + assert.Equal(t, &ConstantRates{}, currencyConverter.Rates(), "Rates should return constant rates") + assert.Equal(t, time.Time{}, currencyConverter.LastUpdated(), "LastUpdated return is incorrect") - time.Sleep(200 * time.Millisecond) - // Rates have been invalid for ~300ms, rates are stale - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + // Second Update call is successful and yields valid rates + err2 := currencyConverter.Run() + assert.Nil(t, err2) - time.Sleep(300 * time.Millisecond) - // Rates have been valid again for ~100ms - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") -} + // Verify rates are valid and last update timestamp is set + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") + assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set") -func TestUseConstantRatesUntilFetchIsSuccessful(t *testing.T) { - callCnt := 0 - mockedHttpServer := httptest.NewServer(http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - if callCnt >= 5 { - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(getMockRates())) - } else { - rw.WriteHeader(http.StatusNotFound) - } - callCnt++ - }), - ) + // Advance time so the rates fall just short of being considered stale + fakeTime.time = fakeTime.time.Add(29 * time.Second) - defer mockedHttpServer.Close() + // Third Update call results in error but stale rate threshold has not been exceeded + err3 := currencyConverter.Run() + assert.NotNil(t, err3) - expectedRates := ¤cies.Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), - Conversions: map[string]map[string]float64{ - "USD": { - "GBP": 0.77208, - }, - "GBP": { - "USD": 1.2952, - }, - }, - } + // Verify rates are valid and last update ts has not changed + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") + assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set") - // Execute: - currencyConverter := currencies.NewRateConverter( - &http.Client{}, - mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(1)*time.Second, - ) + // Advance time just past the threshold so the rates are considered stale + fakeTime.time = fakeTime.time.Add(2 * time.Second) + + // Fourth Update call results in error and stale rate threshold has been exceeded + err4 := currencyConverter.Run() + assert.NotNil(t, err4) - // Verify: - // Rates are invalid at t=0 and remain invalid until 500ms have elapsed - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + // Verify constant rates are used and last update ts has not changed + assert.Equal(t, &ConstantRates{}, currencyConverter.Rates(), "Rates should return constant rates") + assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated return is incorrect") - time.Sleep(400 * time.Millisecond) - // Rates have been invalid for ~400ms - assert.Equal(t, currencyConverter.Rates(), ¤cies.ConstantRates{}, "Rates() should return constant rates") + // Fifth Update call is successful and yields valid rates + err5 := currencyConverter.Run() + assert.Nil(t, err5) - time.Sleep(200 * time.Millisecond) - // Rates have been valid for ~100ms - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + // Verify rates are valid and last update ts has changed + thirtyOneSec := 31 * time.Second + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") + assert.Equal(t, (initialFakeTime.Add(thirtyOneSec)), currencyConverter.LastUpdated(), "LastUpdated should be set") } -func TestRatesAreNeverStale(t *testing.T) { - callCnt := 0 +func TestRatesAreNeverConsideredStale(t *testing.T) { + callCount := 0 mockedHttpServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - if callCnt == 0 { + callCount++ + if callCount == 1 { rw.WriteHeader(http.StatusOK) rw.Write([]byte(getMockRates())) } else { rw.WriteHeader(http.StatusNotFound) + rw.Write([]byte(`{"message": "Not Found"}`)) } - callCnt++ }), ) defer mockedHttpServer.Close() - expectedRates := ¤cies.Rates{ + expectedRates := &Rates{ DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), Conversions: map[string]map[string]float64{ "USD": { @@ -557,25 +268,38 @@ func TestRatesAreNeverStale(t *testing.T) { }, } + initialFakeTime := time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC) + fakeTime := &FakeTime{time: initialFakeTime} + // Execute: - currencyConverter := currencies.NewRateConverter( + currencyConverter := NewRateConverter( &http.Client{}, mockedHttpServer.URL, - time.Duration(100)*time.Millisecond, - time.Duration(0)*time.Millisecond, + 0*time.Millisecond, // stale rates threshold ) + currencyConverter.time = fakeTime + + // First Update call is successful and yields valid rates + err1 := currencyConverter.Run() + assert.Nil(t, err1) + + // Verify rates are valid and last update timestamp is correct + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") + assert.Equal(t, fakeTime.time, currencyConverter.LastUpdated(), "LastUpdated should be set") + + // Advance time so the current time is well past the the time the rates were last updated + fakeTime.time = initialFakeTime.Add(24 * time.Hour) - // Verify: - // Rates are valid at t=0 and are then invalid at 100ms - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + // Second Update call results in error but rates from a day ago are still valid + err2 := currencyConverter.Run() + assert.NotNil(t, err2) - time.Sleep(500 * time.Millisecond) - // Rates have been invalid for ~400ms - assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates") + // Verify rates are valid and last update ts is correct + assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones") + assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set") } func TestRace(t *testing.T) { - // This test is checking that no race conditions appear in rate converter. // It simulate multiple clients (in different goroutines) asking for updates // and rates while the rate converter is also updating periodically. @@ -599,20 +323,19 @@ func TestRace(t *testing.T) { } // Execute: - - // Create a rate converter which will be fetching new values every 10 ms - currencyConverter := currencies.NewRateConverter( + // Create a rate converter which will be fetching new values every 1 ms + interval := 1 * time.Millisecond + currencyConverter := NewRateConverter( mockedHttpClient, "currency.fake.com", - time.Duration(10)*time.Millisecond, - time.Duration(24)*time.Hour, + 24*time.Hour, ) - defer currencyConverter.StopPeriodicFetching() + ticker := task.NewTickerTask(interval, currencyConverter) + ticker.Start() + defer ticker.Stop() - // Create 50 clients asking for updates and rates conversion at random intervals - // from 1ms to 50ms for 10 seconds var wg sync.WaitGroup - clientsCount := 50 + clientsCount := 10 wg.Add(clientsCount) dones := make([]chan bool, clientsCount) @@ -623,12 +346,9 @@ func TestRace(t *testing.T) { clientTicker := time.NewTicker(randomTickInterval) for { select { - case tickTime := <-clientTicker.C: - // Either ask for an Update() or for GetRate() - // based on the tick ms - tickMs := tickTime.UnixNano() / int64(time.Millisecond) - if tickMs%2 == 0 { - err := currencyConverter.Update() + case <-clientTicker.C: + if clientNum < 5 { + err := currencyConverter.Run() assert.Nil(t, err) } else { rate, err := currencyConverter.Rates().GetRate("USD", "GBP") @@ -643,7 +363,7 @@ func TestRace(t *testing.T) { }(dones[c], c) } - time.Sleep(10 * time.Second) + time.Sleep(100 * time.Millisecond) // Sending stop signals to all clients for i := range dones { dones[i] <- true diff --git a/currencies/rates_test.go b/currencies/rates_test.go index 915b817d7a5..5b1c4497b63 100644 --- a/currencies/rates_test.go +++ b/currencies/rates_test.go @@ -146,6 +146,7 @@ func TestGetRate(t *testing.T) { {from: "", to: "EUR", expectedRate: 0, hasError: true}, {from: "CNY", to: "", expectedRate: 0, hasError: true}, {from: "", to: "", expectedRate: 0, hasError: true}, + {from: "USD", to: "USD", expectedRate: 1, hasError: false}, } for _, tc := range testCases { diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index 745dbe3e7d4..90650cc2886 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -24,7 +24,7 @@ type rateConverter interface { } // newCurrencyRatesInfo creates a new CurrencyRatesInfo instance. -func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo { +func newCurrencyRatesInfo(rateConverter rateConverter, fetchingInterval time.Duration) currencyRatesInfo { currencyRatesInfo := currencyRatesInfo{ Active: false, @@ -44,7 +44,6 @@ func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo { source := infos.Source() currencyRatesInfo.Source = &source - fetchingInterval := infos.FetchingInterval() currencyRatesInfo.FetchingInterval = &fetchingInterval lastUpdated := infos.LastUpdated() @@ -57,8 +56,8 @@ func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo { } // NewCurrencyRatesEndpoint returns current currency rates applied by the PBS server. -func NewCurrencyRatesEndpoint(rateConverter rateConverter) http.HandlerFunc { - currencyRateInfo := newCurrencyRatesInfo(rateConverter) +func NewCurrencyRatesEndpoint(rateConverter rateConverter, fetchingInterval time.Duration) http.HandlerFunc { + currencyRateInfo := newCurrencyRatesInfo(rateConverter, fetchingInterval) return func(w http.ResponseWriter, _ *http.Request) { jsonOutput, err := json.Marshal(currencyRateInfo) diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go index e0b127fcd95..86c4e50fb3e 100644 --- a/endpoints/currency_rates_test.go +++ b/endpoints/currency_rates_test.go @@ -14,20 +14,21 @@ import ( func TestCurrencyRatesEndpoint(t *testing.T) { // Setup: var testCases = []struct { - input rateConverter - expectedBody string - expectedCode int - description string + inputConverter rateConverter + inputFetchingInterval time.Duration + expectedBody string + expectedCode int + description string }{ { nil, + time.Duration(0), `{"active": false}`, http.StatusOK, "case 1 - rate converter is nil", }, { newRateConverterMock( - 5*time.Minute, "https://sync.test.com", time.Date(2019, 3, 2, 12, 54, 56, 651387237, time.UTC), newConversionMock(&map[string]map[string]float64{ @@ -36,6 +37,7 @@ func TestCurrencyRatesEndpoint(t *testing.T) { }, }), ), + 5 * time.Minute, `{ "active": true, "source": "https://sync.test.com", @@ -52,11 +54,11 @@ func TestCurrencyRatesEndpoint(t *testing.T) { }, { newRateConverterMock( - time.Duration(0), "", time.Time{}, nil, ), + time.Duration(0), `{ "active": true, "source": "", @@ -70,12 +72,14 @@ func TestCurrencyRatesEndpoint(t *testing.T) { newRateConverterMockWithInfo( newUnmarshableConverterInfoMock(), ), + time.Duration(0), "", http.StatusInternalServerError, "case 4 - invalid rates input for marshaling", }, { newRateConverterMockWithNilInfo(), + time.Duration(0), `{ "active": true }`, @@ -86,7 +90,7 @@ func TestCurrencyRatesEndpoint(t *testing.T) { for _, tc := range testCases { - handler := NewCurrencyRatesEndpoint(tc.input) + handler := NewCurrencyRatesEndpoint(tc.inputConverter, tc.inputFetchingInterval) w := httptest.NewRecorder() // Execute: @@ -117,21 +121,16 @@ func newConversionMock(rates *map[string]map[string]float64) *conversionMock { } type converterInfoMock struct { - source string - fetchingInterval time.Duration - lastUpdated time.Time - rates *map[string]map[string]float64 - additionalInfo interface{} + source string + lastUpdated time.Time + rates *map[string]map[string]float64 + additionalInfo interface{} } func (m converterInfoMock) Source() string { return m.source } -func (m converterInfoMock) FetchingInterval() time.Duration { - return m.fetchingInterval -} - func (m converterInfoMock) LastUpdated() time.Time { return m.lastUpdated } @@ -150,10 +149,6 @@ func (m unmarshableConverterInfoMock) Source() string { return "" } -func (m unmarshableConverterInfoMock) FetchingInterval() time.Duration { - return time.Duration(0) -} - func (m unmarshableConverterInfoMock) LastUpdated() time.Time { return time.Time{} } @@ -172,7 +167,6 @@ func newUnmarshableConverterInfoMock() unmarshableConverterInfoMock { } type rateConverterMock struct { - fetchingInterval time.Duration syncSourceURL string rates *conversionMock lastUpdated time.Time @@ -197,23 +191,20 @@ func (m rateConverterMock) GetInfo() currencies.ConverterInfo { rates = m.rates.GetRates() } return converterInfoMock{ - source: m.syncSourceURL, - fetchingInterval: m.fetchingInterval, - lastUpdated: m.lastUpdated, - rates: rates, + source: m.syncSourceURL, + lastUpdated: m.lastUpdated, + rates: rates, } } func newRateConverterMock( - fetchingInterval time.Duration, syncSourceURL string, lastUpdated time.Time, rates *conversionMock) rateConverterMock { return rateConverterMock{ - fetchingInterval: fetchingInterval, - syncSourceURL: syncSourceURL, - rates: rates, - lastUpdated: lastUpdated, + syncSourceURL: syncSourceURL, + rates: rates, + lastUpdated: lastUpdated, } } diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index 93d7575e865..fba0daecea8 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "strings" "testing" + "time" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currencies" @@ -77,7 +78,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { theMetrics, infos, gdpr.AlwaysAllow{}, - currencies.NewRateConverterDefault(), + currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)), ), paramValidator, empty_fetcher.EmptyFetcher{}, diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 7ae96c09b93..4f207cf5a65 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -75,7 +75,7 @@ func TestSingleBidder(t *testing.T) { bidResponse: mockBidderResponse, } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) // Make sure the goodSingleBidder was called with the expected arguments. @@ -163,7 +163,7 @@ func TestMultiBidder(t *testing.T) { bidResponse: mockBidderResponse, } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if seatBid == nil { @@ -528,9 +528,11 @@ func TestMultiCurrencies(t *testing.T) { currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, - time.Duration(10)*time.Second, time.Duration(24)*time.Hour, ) + time.Sleep(time.Duration(500) * time.Millisecond) + currencyConverter.Run() + seatBid, errs := bidder.requestBid( context.Background(), &openrtb.BidRequest{}, @@ -674,7 +676,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { // Execute: bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid( context.Background(), &openrtb.BidRequest{}, @@ -843,7 +845,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { currencyConverter := currencies.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, - time.Duration(10)*time.Second, time.Duration(24)*time.Hour, ) seatBid, errs := bidder.requestBid( @@ -1020,7 +1021,7 @@ func TestMobileNativeTypes(t *testing.T) { bidResponse: tc.mockBidderResponse, } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBids, _ := bidder.requestBid( context.Background(), @@ -1041,7 +1042,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") @@ -1224,7 +1225,8 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus) - _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencies.NewRateConverterDefault().Rates(), &adapters.ExtraRequestInfo{}) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 5fbdb1c57a9..545f04fd0ef 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -50,7 +50,8 @@ func TestNewExchange(t *testing.T) { Adapters: blankAdapterConfig(openrtb_ext.BidderList()), } - e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), knownAdapters, config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), knownAdapters, config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) @@ -87,7 +88,8 @@ func TestCharacterEscape(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ //liveAdapters []openrtb_ext.BidderName, @@ -230,7 +232,7 @@ func TestDebugBehaviour(t *testing.T) { e.cache = &wellBehavedCache{} e.me = &metricsConf.DummyMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} - e.currencyConverter = currencies.NewRateConverterDefault() + e.currencyConverter = currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) // Run tests for _, test := range testCases { @@ -299,7 +301,8 @@ func TestGetBidCacheInfo(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -449,7 +452,8 @@ func TestBidResponseCurrency(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -616,7 +620,8 @@ func TestRaceIntegration(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter) _, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -700,7 +705,8 @@ func TestPanicRecovery(t *testing.T) { } theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - e := NewExchange(&http.Client{}, nil, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(&http.Client{}, nil, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) { panic("panic!") @@ -765,7 +771,8 @@ func TestPanicRecoveryHighLevel(t *testing.T) { Endpoint: server.URL, } } - e := NewExchange(server.Client(), &mockCache{}, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange) + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e := NewExchange(server.Client(), &mockCache{}, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -1025,7 +1032,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] cache: &wellBehavedCache{}, cacheTime: 0, gDPR: gdpr.AlwaysAllow{}, - currencyConverter: currencies.NewRateConverterDefault(), + currencyConverter: currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)), UsersyncIfAmbiguous: false, privacyConfig: privacyConfig, } diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go index 3ca804a115c..61414c0ed73 100644 --- a/exchange/legacy_test.go +++ b/exchange/legacy_test.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "errors" + "net/http" "reflect" "testing" + "time" "github.com/buger/jsonparser" "github.com/evanphx/json-patch" @@ -58,7 +60,7 @@ func TestSiteVideo(t *testing.T) { mockAdapter := mockLegacyAdapter{} exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if len(errs) > 0 { t.Errorf("Unexpected error requesting bids: %v", errs) @@ -92,7 +94,7 @@ func TestAppBanner(t *testing.T) { mockAdapter := mockLegacyAdapter{} exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if len(errs) > 0 { t.Errorf("Unexpected error requesting bids: %v", errs) @@ -138,7 +140,7 @@ func TestBidTransforms(t *testing.T) { } exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if len(errs) != 1 { t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) @@ -287,7 +289,7 @@ func TestErrorResponse(t *testing.T) { } exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if len(errs) != 1 { t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) @@ -326,7 +328,7 @@ func TestWithTargeting(t *testing.T) { }}, } exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currencies.NewRateConverterDefault() + currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderFacebook, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) if len(errs) != 0 { t.Fatalf("This should not produce errors. Got %v", errs) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 284d56be42e..e596e5aa215 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -88,7 +88,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, - currencyConverter: currencies.NewRateConverterDefault(), + currencyConverter: currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)), UsersyncIfAmbiguous: false, } diff --git a/main.go b/main.go index 9a835f42a4c..035d386e3b0 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" + "github.com/prebid/prebid-server/util/task" "github.com/golang/glog" "github.com/spf13/viper" @@ -53,8 +54,10 @@ func loadConfig() (*config.Configuration, error) { func serve(revision string, cfg *config.Configuration) error { fetchingInterval := time.Duration(cfg.CurrencyConverter.FetchIntervalSeconds) * time.Second staleRatesThreshold := time.Duration(cfg.CurrencyConverter.StaleRatesSeconds) * time.Second - currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, - fetchingInterval, staleRatesThreshold) + currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, staleRatesThreshold) + + currencyConverterTickerTask := task.NewTickerTask(fetchingInterval, currencyConverter) + currencyConverterTickerTask.Start() r, err := router.New(cfg, currencyConverter) if err != nil { @@ -64,7 +67,7 @@ func serve(revision string, cfg *config.Configuration) error { pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) corsRouter := router.SupportCORS(r) - server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter), r.MetricsEngine) + server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine) r.Shutdown() return nil diff --git a/router/admin.go b/router/admin.go index 83c4701bb19..fe268c48b2c 100644 --- a/router/admin.go +++ b/router/admin.go @@ -3,12 +3,13 @@ package router import ( "net/http" "net/http/pprof" + "time" "github.com/prebid/prebid-server/currencies" "github.com/prebid/prebid-server/endpoints" ) -func Admin(revision string, rateConverter *currencies.RateConverter) *http.ServeMux { +func Admin(revision string, rateConverter *currencies.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { // Add endpoints to the admin server // Making sure to add pprof routes mux := http.NewServeMux() @@ -19,7 +20,7 @@ func Admin(revision string, rateConverter *currencies.RateConverter) *http.Serve mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) // Register prebid-server defined admin handlers - mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter)) + mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter, rateConverterFetchingInterval)) mux.HandleFunc("/version", endpoints.NewVersionEndpoint(revision)) return mux } diff --git a/util/task/ticker_task.go b/util/task/ticker_task.go new file mode 100644 index 00000000000..a8d523b75d5 --- /dev/null +++ b/util/task/ticker_task.go @@ -0,0 +1,53 @@ +package task + +import ( + "time" +) + +type Runner interface { + Run() error +} + +type TickerTask struct { + interval time.Duration + runner Runner + done chan struct{} +} + +func NewTickerTask(interval time.Duration, runner Runner) *TickerTask { + return &TickerTask{ + interval: interval, + runner: runner, + done: make(chan struct{}), + } +} + +// Start runs the task immediately and then schedules the task to run periodically +// if a positive fetching interval has been specified. +func (t *TickerTask) Start() { + t.runner.Run() + + if t.interval > 0 { + go t.runRecurring() + } +} + +// Stop stops the periodic task but the task runner maintains state +func (t *TickerTask) Stop() { + close(t.done) +} + +// run creates a ticker that ticks at the specified interval. On each tick, +// the task is executed +func (t *TickerTask) runRecurring() { + ticker := time.NewTicker(t.interval) + + for { + select { + case <-ticker.C: + t.runner.Run() + case <-t.done: + return + } + } +} diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go new file mode 100644 index 00000000000..27551c9a2c2 --- /dev/null +++ b/util/task/ticker_task_test.go @@ -0,0 +1,63 @@ +package task_test + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/util/task" + "github.com/stretchr/testify/assert" +) + +type MockRunner struct { + RunCount int +} + +func (mcc *MockRunner) Run() error { + mcc.RunCount++ + return nil +} + +func TestStartWithSingleRun(t *testing.T) { + // Setup: + runner := &MockRunner{RunCount: 0} + interval := 0 * time.Millisecond + ticker := task.NewTickerTask(interval, runner) + + // Execute: + ticker.Start() + time.Sleep(10 * time.Millisecond) + + // Verify: + assert.Equal(t, runner.RunCount, 1, "runner should have run one time") +} + +func TestStartWithPeriodicRun(t *testing.T) { + // Setup: + runner := &MockRunner{RunCount: 0} + interval := 10 * time.Millisecond + ticker := task.NewTickerTask(interval, runner) + + // Execute: + ticker.Start() + time.Sleep(25 * time.Millisecond) + ticker.Stop() + + // Verify: + assert.Equal(t, runner.RunCount, 3, "runner should have run three times") +} + +func TestStop(t *testing.T) { + // Setup: + runner := &MockRunner{RunCount: 0} + interval := 10 * time.Millisecond + ticker := task.NewTickerTask(interval, runner) + + // Execute: + ticker.Start() + time.Sleep(25 * time.Millisecond) + ticker.Stop() + time.Sleep(25 * time.Millisecond) // wait in case stop failed so additional runs can happen + + // Verify: + assert.Equal(t, runner.RunCount, 3, "runner should have run three times") +} diff --git a/util/timeutil/time.go b/util/timeutil/time.go new file mode 100644 index 00000000000..e8eaae7d61f --- /dev/null +++ b/util/timeutil/time.go @@ -0,0 +1,16 @@ +package timeutil + +import ( + "time" +) + +type Time interface { + Now() time.Time +} + +// RealTime wraps the time package for testability +type RealTime struct{} + +func (c *RealTime) Now() time.Time { + return time.Now() +} From a4ac6b63f312ac94d4844b7129fcf8f3dd044204 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 12 Aug 2020 18:57:54 -0400 Subject: [PATCH 166/603] Fix TCF1 Fetcher Fallback (#1438) --- gdpr/vendorlist-fetching.go | 2 +- gdpr/vendorlist-fetching_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index a0a73c93008..1442f81c3ba 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -158,7 +158,7 @@ func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list ap if ok { return list.(vendorlist.VendorList) } - return fallbackVL + return nil } return } diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index c989ef4cef8..031e564094c 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -173,7 +173,7 @@ func TestDefaultVendorList(t *testing.T) { assert.Equal(t, false, vendor.Purpose(2)) } -func TestDefaultVendorListPassthrough(t *testing.T) { +func TestFallbackVendorListPassthrough(t *testing.T) { firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ 32: { purposes: []int{1, 2}, @@ -184,7 +184,7 @@ func TestDefaultVendorListPassthrough(t *testing.T) { purposes: []int{2}, }, }) - server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ + server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ 1: firstVendorList, 2: secondVendorList, }))) @@ -204,7 +204,7 @@ func TestDefaultVendorListPassthrough(t *testing.T) { assert.Equal(t, true, vendor.Purpose(2)) } -func TestDefaultVendorListNoFetch(t *testing.T) { +func TestFallbackVendorListNoFetch(t *testing.T) { firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ 32: { purposes: []int{1, 2}, From 5a7d3652d448e179be7add376047b562c115d0ef Mon Sep 17 00:00:00 2001 From: chino117 Date: Mon, 17 Aug 2020 11:09:22 -0300 Subject: [PATCH 167/603] Eplanning adapter: Get domain from page (#1434) --- adapters/eplanning/eplanning.go | 25 ++++--- .../supplemental/bad-page-site.json | 31 ++++++++ .../site-page-and-url-correctly-parsed.json | 75 +++++++++++++++++++ 3 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 adapters/eplanning/eplanningtest/supplemental/bad-page-site.json create mode 100644 adapters/eplanning/eplanningtest/supplemental/site-page-and-url-correctly-parsed.json diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 2a46b5469e0..032edfd1b06 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -104,25 +104,28 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn } } - var pageURL string + pageURL := defaultPageURL if request.Site != nil && request.Site.Page != "" { pageURL = request.Site.Page - } else { - pageURL = defaultPageURL } - var pageDomain string - if request.Site != nil && request.Site.Domain != "" { - pageDomain = request.Site.Domain - } else { - pageDomain = defaultPageURL + pageDomain := defaultPageURL + if request.Site != nil { + if request.Site.Domain != "" { + pageDomain = request.Site.Domain + } else if request.Site.Page != "" { + u, err := url.Parse(request.Site.Page) + if err != nil { + errors = append(errors, err) + return nil, errors + } + pageDomain = u.Hostname() + } } - var requestTarget string + requestTarget := pageDomain if request.App != nil && request.App.Bundle != "" { requestTarget = request.App.Bundle - } else { - requestTarget = pageDomain } uriObj, err := url.Parse(adapter.URI) diff --git a/adapters/eplanning/eplanningtest/supplemental/bad-page-site.json b/adapters/eplanning/eplanningtest/supplemental/bad-page-site.json new file mode 100644 index 00000000000..5efe604f7e6 --- /dev/null +++ b/adapters/eplanning/eplanningtest/supplemental/bad-page-site.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "bidder": { + "ci": "12345", + "adunit_code": "test_adunitcode" + } + } + } + ], + "site": { + "page": "http://www.page%test.com" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "parse (\\\")?http://www.page%test.com(\\\")?: invalid URL escape \\\"%te\\\"", + "comparison": "regex" + } + ] +} + diff --git a/adapters/eplanning/eplanningtest/supplemental/site-page-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-page-and-url-correctly-parsed.json new file mode 100644 index 00000000000..20a419cdbfd --- /dev/null +++ b/adapters/eplanning/eplanningtest/supplemental/site-page-and-url-correctly-parsed.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "bidder": { + "ci": "12345", + "adunit_code": "test_adunitcode" + } + } + } + ], + "site": { + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.e-planning.net/pbs/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere", + "body": {} + }, + "mockResponse": { + "status": 200, + "body": { + "sI": { "k": "12345" }, + "sec": "ROS", + "sp": [ + { + "k": "testadunitcode", + "a": [{ + "i": "123456789abcdef", + "pr": "0.5", + "adm": "
test
", + "crid": "abcdef123456789", + "id": "adid12345", + "w": 600, + "h": 300 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "123456789abcdef", + "impid": "test-imp-id", + "price": 0.5, + "adm": "
test
", + "adid": "adid12345", + "crid": "abcdef123456789", + "w": 600, + "h": 300 + }, + "type": "banner" + } + ] + } + ] + } + From e065488276139a56adf9500908bb93fc70c1ac91 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Mon, 17 Aug 2020 08:17:15 -0700 Subject: [PATCH 168/603] Fix no bid debug log (#1375) --- endpoints/openrtb2/video_auction.go | 45 +++++------- endpoints/openrtb2/video_auction_test.go | 71 ++++++++++++++++++ exchange/auction.go | 51 ++++++++++++- .../debuglog_enabled_no_winners_nor_bids.json | 54 ++++++++++++++ exchange/exchange.go | 18 +++++ .../debuglog_enabled_no_bids.json | 72 +++++++++++++++++++ openrtb_ext/bid_response_video.go | 6 +- 7 files changed, 283 insertions(+), 34 deletions(-) create mode 100644 exchange/cachetest/debuglog_enabled_no_winners_nor_bids.json create mode 100644 exchange/exchangetest/debuglog_enabled_no_bids.json diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 49ba287610b..a6ca527874a 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -122,7 +122,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { - err := putDebugLogError(deps.cache, &debugLog, start) + err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) if err != nil { vo.Errors = append(vo.Errors, err) } @@ -279,6 +279,21 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidResp.Ext = response.Ext } + if len(bidResp.AdPods) == 0 && debugLog.Enabled { + err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) + if err != nil { + vo.Errors = append(vo.Errors, err) + } else { + bidResp.AdPods = append(bidResp.AdPods, &openrtb_ext.AdPod{ + Targeting: []openrtb_ext.VideoTargeting{ + { + HbCacheID: debugLog.CacheKey, + }, + }, + }) + } + } + vo.VideoResponse = bidResp resp, err := json.Marshal(bidResp) @@ -294,34 +309,6 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } -func putDebugLogError(cache prebid_cache_client.Client, debugLog *exchange.DebugLog, start time.Time) error { - debugLog.Data.Response = "No response created" - - debugLog.BuildCacheString() - - data, err := json.Marshal(debugLog.CacheString) - if err != nil { - return err - } - - toCache := []prebid_cache_client.Cacheable{ - { - Type: debugLog.CacheType, - Data: data, - TTLSeconds: debugLog.TTL, - Key: "log_" + debugLog.CacheKey, - }, - } - - if cache != nil { - ctx, cancel := context.WithDeadline(context.Background(), start.Add(time.Duration(100)*time.Millisecond)) - defer cancel() - cache.PutJson(ctx, toCache) - } - - return nil -} - func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) *openrtb_ext.BidRequestVideo { for i := len(podErrors) - 1; i >= 0; i-- { videoReq.PodConfig.Pods = append(videoReq.PodConfig.Pods[:podErrors[i].PodIndex], videoReq.PodConfig.Pods[podErrors[i].PodIndex+1:]...) diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index b15c6a7b47a..534db3c79e2 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -284,6 +284,42 @@ func TestVideoEndpointDebugError(t *testing.T) { assert.Equal(t, recorder.Code, 500, "Should catch error in request") } +func TestVideoEndpointDebugNoAdPods(t *testing.T) { + ex := &mockExchangeVideoNoBids{ + cache: &mockCacheClient{}, + } + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + reqBody := string(getRequestPayload(t, reqData)) + req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps := mockDepsNoBids(t, ex) + deps.VideoAuctionEndpoint(recorder, req, nil) + + if ex.lastRequest == nil { + t.Fatalf("The request never made it into the Exchange.") + } + if !ex.cache.called { + t.Fatalf("Cache was not called when it should have been") + } + + respBytes := recorder.Body.Bytes() + resp := &openrtb_ext.BidResponseVideo{} + if err := json.Unmarshal(respBytes, resp); err != nil { + t.Fatalf("Unable to unmarshal response.") + } + + assert.Len(t, resp.AdPods, 1, "Debug AdPod should be added to response") + assert.Empty(t, resp.AdPods[0].Errors, "AdPod Errors should be empty") + assert.Empty(t, resp.AdPods[0].Targeting[0].HbPb, "Hb_pb should be empty") + assert.Empty(t, resp.AdPods[0].Targeting[0].HbPbCatDur, "Hb_pb_cat_dur should be empty") + assert.NotEmpty(t, resp.AdPods[0].Targeting[0].HbCacheID, "Hb_cache_id should not be empty") + assert.Equal(t, int64(0), resp.AdPods[0].PodId, "Pod ID should be 0") +} + func TestVideoEndpointNoPods(t *testing.T) { ex := &mockExchangeVideo{} reqData, err := ioutil.ReadFile("sample-requests/video/video_invalid_sample.json") @@ -1189,6 +1225,29 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { return deps } +func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { + theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + edep := &endpointDeps{ + ex, + newParamsValidator(t), + &mockVideoStoredReqFetcher{}, + &mockVideoStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + theMetrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + ex.cache, + regexp.MustCompile(`[<>]`), + hardcodedResponseIPValidator{response: true}, + } + + return edep +} + type mockCacheClient struct { called bool } @@ -1247,6 +1306,18 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb }, nil } +type mockExchangeVideoNoBids struct { + lastRequest *openrtb.BidRequest + cache *mockCacheClient +} + +func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { + m.lastRequest = bidRequest + return &openrtb.BidResponse{ + SeatBid: []openrtb.SeatBid{{}}, + }, nil +} + var testVideoStoredImpData = map[string]json.RawMessage{ "fba10607-0c12-43d1-ad07-b8a513bc75d6": json.RawMessage(`{"ext": {"appnexus": {"placementId": 14997137}}}`), "8b452b41-2681-4a20-9086-6f16ffad7773": json.RawMessage(`{"ext": {"appnexus": {"placementId": 15016213}}}`), diff --git a/exchange/auction.go b/exchange/auction.go index 45e1422540e..aa446ddba13 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -8,6 +8,7 @@ import ( "fmt" "regexp" "strings" + "time" uuid "github.com/gofrs/uuid" "github.com/golang/glog" @@ -47,6 +48,52 @@ func (d *DebugLog) BuildCacheString() { d.CacheString = fmt.Sprintf("%s%s%s%s", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response) } +func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout int, errors []error) error { + if len(d.Data.Response) == 0 && len(errors) == 0 { + d.Data.Response = "No response or errors created" + } + + if len(errors) > 0 { + errStrings := []string{} + for _, err := range errors { + errStrings = append(errStrings, err.Error()) + } + d.Data.Response = fmt.Sprintf("%s\nErrors:\n%s", d.Data.Response, strings.Join(errStrings, "\n")) + } + + d.BuildCacheString() + + if len(d.CacheKey) == 0 { + rawUUID, err := uuid.NewV4() + if err != nil { + return err + } + d.CacheKey = rawUUID.String() + } + + data, err := json.Marshal(d.CacheString) + if err != nil { + return err + } + + toCache := []prebid_cache_client.Cacheable{ + { + Type: d.CacheType, + Data: data, + TTLSeconds: d.TTL, + Key: "log_" + d.CacheKey, + }, + } + + if cache != nil { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Duration(timeout)*time.Millisecond)) + defer cancel() + cache.PutJson(ctx, toCache) + } + + return nil +} + func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int) *auction { winningBids := make(map[string]*pbsOrtbBid, numImps) winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*pbsOrtbBid, numImps) @@ -179,9 +226,9 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } - if debugLog != nil && debugLog.Enabled { - debugLog.BuildCacheString() + if len(toCache) > 0 && debugLog != nil && debugLog.Enabled { debugLog.CacheKey = hbCacheID + debugLog.BuildCacheString() if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { toCache = append(toCache, prebid_cache_client.Cacheable{ Type: debugLog.CacheType, diff --git a/exchange/cachetest/debuglog_enabled_no_winners_nor_bids.json b/exchange/cachetest/debuglog_enabled_no_winners_nor_bids.json new file mode 100644 index 00000000000..637b33e171b --- /dev/null +++ b/exchange/cachetest/debuglog_enabled_no_winners_nor_bids.json @@ -0,0 +1,54 @@ +{ + "debugLog": { + "Enabled": true, + "CacheType": "xml", + "TTL": 3600, + "Data": { + "Request": "test request string", + "Headers": "test headers string", + "Response": "test response string" + } + }, + "bidRequest": { + "imp": [ + { + "id": "oneImp", + "exp": 600 + }, + { + "id": "twoImp" + } + ] + }, + "pbsBids": [ + { + "bid": { + "id": "bidOne", + "impid": "oneImp", + "price": 7.64 + }, + "bidType": "video", + "bidder": "appnexus" + }, + { + "bid": { + "id": "bidTwo", + "impid": "twoImp", + "price": 5.64 + }, + "bidType": "video", + "bidder": "pubmatic" + } + ], + "expectedCacheables": [], + "defaultTTLs": { + "banner": 300, + "video": 3600, + "audio": 1800, + "native": 300 + }, + "targetDataIncludeWinners": false, + "targetDataIncludeBidderKeys": false, + "targetDataIncludeCacheBids": true, + "targetDataIncludeCacheVast": false +} \ No newline at end of file diff --git a/exchange/exchange.go b/exchange/exchange.go index 57e13644163..cf5ec9cc000 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,6 +14,7 @@ import ( "strings" "time" + uuid "github.com/gofrs/uuid" "github.com/prebid/prebid-server/stored_requests" "github.com/golang/glog" @@ -192,6 +193,23 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } + if !anyBidsReturned { + if debugLog != nil && debugLog.Enabled { + if rawUUID, err := uuid.NewV4(); err == nil { + debugLog.CacheKey = rawUUID.String() + + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, debugInfo, errs) + if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + debugLog.Data.Response = string(bidRespExtBytes) + } else { + debugLog.Data.Response = "Unable to marshal response ext for debugging" + } + } else { + errs = append(errs, err) + } + } + } + // Build the response return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, adapterExtra, auc, bidResponseExt, errs) } diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json new file mode 100644 index 00000000000..4823acf8f16 --- /dev/null +++ b/exchange/exchangetest/debuglog_enabled_no_bids.json @@ -0,0 +1,72 @@ +{ + "debugLog": { + "Enabled": true, + "CacheType": "xml", + "TTL": 3600, + "Data": { + "Request": "test request string", + "Headers": "test headers string", + "Response": "" + } + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + } + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": {} + } + } + }, + "response": { + "bids": {} + } +} \ No newline at end of file diff --git a/openrtb_ext/bid_response_video.go b/openrtb_ext/bid_response_video.go index 4c123498ec8..22661547ca7 100644 --- a/openrtb_ext/bid_response_video.go +++ b/openrtb_ext/bid_response_video.go @@ -14,7 +14,7 @@ type AdPod struct { } type VideoTargeting struct { - HbPb string `json:"hb_pb"` - HbPbCatDur string `json:"hb_pb_cat_dur"` - HbCacheID string `json:"hb_cache_id"` + HbPb string `json:"hb_pb,omitempty"` + HbPbCatDur string `json:"hb_pb_cat_dur,omitempty"` + HbCacheID string `json:"hb_cache_id,omitempty"` } From 2e9d8337d32971d4c8e03b3a68dd69d782610cb6 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Mon, 17 Aug 2020 12:09:51 -0400 Subject: [PATCH 169/603] Update the fallback GVL to last version (#1440) --- gdpr/vendorlist-fetching_test.go | 4 ++-- static/tcf1/fallback_gvl.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 031e564094c..484a0a54b41 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -165,7 +165,7 @@ func TestDefaultVendorList(t *testing.T) { list, err := fetcher(context.Background(), 12) assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) - assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version()) + assert.Equal(t, uint16(215), list.Version(), "Expected to fetch default version 215, got %d", list.Version()) // Testing that we got the default vendorlist data, and not the version off the server. vendor := list.Vendor(12) @@ -227,7 +227,7 @@ func TestFallbackVendorListNoFetch(t *testing.T) { fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) list, err := fetcher(context.Background(), 2) assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) - assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version()) + assert.Equal(t, uint16(215), list.Version(), "Expected to fetch default version 215, got %d", list.Version()) // Testing that we got the default vendorlist data, and not the version off the server. vendor := list.Vendor(12) diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json index 86895a52362..9f1c8506b32 100644 --- a/static/tcf1/fallback_gvl.json +++ b/static/tcf1/fallback_gvl.json @@ -1 +1 @@ -{"vendorListVersion":214,"lastUpdated":"2020-08-06T16:00:35Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":360,"name":"Permutive Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy","deletedDate":"2020-03-31T00:00:00Z"},{"id":361,"name":"Permutive","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":384,"name":"Pixalate, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://pixalate.com/privacypolicy/","deletedDate":"2019-11-08T00:00:00Z"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Dr. Banner","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":612,"name":"Adnami Aps","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adnami.io/privacy","deletedDate":"2020-03-17T00:00:00Z"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"}]} \ No newline at end of file +{"vendorListVersion":215,"lastUpdated":"2020-08-13T16:00:19Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Mediakeys Platform","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"},{"id":808,"name":"Pure Local Media GmbH","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://purelocalmedia.de/?page_id=593"}]} \ No newline at end of file From 21b41ff22c9fce7823f061077dd00bc679ddd7b9 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 20 Aug 2020 12:59:27 -0400 Subject: [PATCH 170/603] Enable geo activation of GDPR flag (#1427) --- config/config.go | 9 +++ exchange/exchange.go | 25 +++++++- exchange/exchange_test.go | 27 +++++--- .../exchangetest/gdpr-geo-eu-off-device.json | 64 +++++++++++++++++++ exchange/exchangetest/gdpr-geo-eu-off.json | 60 +++++++++++++++++ exchange/exchangetest/gdpr-geo-eu-on.json | 60 +++++++++++++++++ exchange/exchangetest/gdpr-geo-usa-off.json | 61 ++++++++++++++++++ exchange/exchangetest/gdpr-geo-usa-on.json | 61 ++++++++++++++++++ gdpr/impl.go | 19 ++++++ 9 files changed, 377 insertions(+), 9 deletions(-) create mode 100644 exchange/exchangetest/gdpr-geo-eu-off-device.json create mode 100644 exchange/exchangetest/gdpr-geo-eu-off.json create mode 100644 exchange/exchangetest/gdpr-geo-eu-on.json create mode 100644 exchange/exchangetest/gdpr-geo-usa-off.json create mode 100644 exchange/exchangetest/gdpr-geo-usa-on.json diff --git a/config/config.go b/config/config.go index 7fc77855810..9e6b1370128 100755 --- a/config/config.go +++ b/config/config.go @@ -159,6 +159,11 @@ type GDPR struct { TCF1 TCF1 `mapstructure:"tcf1"` TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` + // EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies. + // If the gdpr flag is unset in a request, but geo.country is set, we will assume GDPR applies if and only + // if the country matches one on this list. If both the GDPR flag and country are not set, we default + // to UsersyncIfAmbiguous + EEACountries []string `mapstructure:"eea_countries"` } func (cfg *GDPR) validate(errs configErrors) configErrors { @@ -903,6 +908,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) + v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", + "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", + "LIE", "LTU", "LUX", "MLT", "MTQ", "MYT", "NLD", "NOR", "POL", "PRT", "REU", "ROU", "BLM", "MAF", "SPM", + "SVK", "SVN", "ESP", "SWE", "GBR"}) v.SetDefault("ccpa.enforce", false) v.SetDefault("lmt.enforce", true) v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") diff --git a/exchange/exchange.go b/exchange/exchange.go index cf5ec9cc000..53f4a7a3e1f 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -55,6 +55,7 @@ type exchange struct { UsersyncIfAmbiguous bool defaultTTLs config.DefaultTTLs privacyConfig config.Privacy + eeaCountries map[string]struct{} } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -75,6 +76,10 @@ type bidResponseWrapper struct { func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter) Exchange { e := new(exchange) + var s struct{} + for _, c := range cfg.GDPR.EEACountries { + e.eeaCountries[c] = s + } e.adapterMap = newAdapterMap(client, cfg, infos, metricsEngine) e.cache = cache e.cacheTime = time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond @@ -121,9 +126,27 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque e.me.RecordImps(impLabels) } + // Make our best guess if GDPR applies + usersyncIfAmbiguous := e.UsersyncIfAmbiguous + var geo *openrtb.Geo = nil + if bidRequest.User != nil && bidRequest.User.Geo != nil { + geo = bidRequest.User.Geo + } else if bidRequest.Device != nil && bidRequest.Device.Geo != nil { + geo = bidRequest.Device.Geo + } + if geo != nil { + // If we have a country set, and it is on the list, we assume GDPR applies if not set on the request. + // Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long). + if _, found := e.eeaCountries[strings.ToUpper(geo.Country)]; found { + usersyncIfAmbiguous = false + } else if len(geo.Country) == 3 { + // The country field is formatted properly as a three character country code + usersyncIfAmbiguous = true + } + } // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig) + cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, usersyncIfAmbiguous, e.privacyConfig) e.me.RecordRequestPrivacy(privacyLabels) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 545f04fd0ef..aad448f397f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -909,6 +909,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { LMT: config.LMT{ Enforce: spec.EnforceLMT, }, + GDPR: config.GDPR{ + UsersyncIfAmbiguous: !spec.AssumeGDPRApplies, + }, } ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig) @@ -1026,15 +1029,22 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } } + var s struct{} + eeac := make(map[string]struct{}) + for _, c := range []string{"FIN", "FRA", "GUF"} { + eeac[c] = s + } + return &exchange{ adapterMap: adapters, me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.BidderList()), cache: &wellBehavedCache{}, cacheTime: 0, - gDPR: gdpr.AlwaysAllow{}, + gDPR: gdpr.AlwaysFail{}, currencyConverter: currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: false, + UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous, privacyConfig: privacyConfig, + eeaCountries: eeac, } } @@ -1882,12 +1892,13 @@ func TestUpdateHbPbCatDur(t *testing.T) { } type exchangeSpec struct { - IncomingRequest exchangeRequest `json:"incomingRequest"` - OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` - Response exchangeResponse `json:"response,omitempty"` - EnforceCCPA bool `json:"enforceCcpa"` - EnforceLMT bool `json:"enforceLmt"` - DebugLog *DebugLog `json:"debuglog,omitempty"` + IncomingRequest exchangeRequest `json:"incomingRequest"` + OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` + Response exchangeResponse `json:"response,omitempty"` + EnforceCCPA bool `json:"enforceCcpa"` + EnforceLMT bool `json:"enforceLmt"` + AssumeGDPRApplies bool `json:"assume_gdpr_applies"` + DebugLog *DebugLog `json:"debuglog,omitempty"` } type exchangeRequest struct { diff --git a/exchange/exchangetest/gdpr-geo-eu-off-device.json b/exchange/exchangetest/gdpr-geo-eu-off-device.json new file mode 100644 index 00000000000..fc655de8162 --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-eu-off-device.json @@ -0,0 +1,64 @@ +{ + "assume_gdpr_applies": false, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id" + }, + "device": { + "geo": { + "country": "FRA" + } + } +} + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + }, + "device": { + "geo": { + "country": "FRA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-eu-off.json b/exchange/exchangetest/gdpr-geo-eu-off.json new file mode 100644 index 00000000000..27a030f11fc --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-eu-off.json @@ -0,0 +1,60 @@ +{ + "assume_gdpr_applies": false, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "FRA" + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + "geo": { + "country": "FRA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-eu-on.json b/exchange/exchangetest/gdpr-geo-eu-on.json new file mode 100644 index 00000000000..4ec42fc6c70 --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-eu-on.json @@ -0,0 +1,60 @@ +{ + "assume_gdpr_applies": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "FRA" + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + "geo": { + "country": "FRA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-usa-off.json b/exchange/exchangetest/gdpr-geo-usa-off.json new file mode 100644 index 00000000000..d56c9318a56 --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-usa-off.json @@ -0,0 +1,61 @@ +{ + "assume_gdpr_applies": false, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "USA" + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "USA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-usa-on.json b/exchange/exchangetest/gdpr-geo-usa-on.json new file mode 100644 index 00000000000..f922be9ea4e --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-usa-on.json @@ -0,0 +1,61 @@ +{ + "assume_gdpr_applies": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "USA" + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "USA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/gdpr/impl.go b/gdpr/impl.go index 2deddc7b2ba..2fbd9c5a07c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -228,3 +228,22 @@ func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext func (a AlwaysAllow) AMPException() bool { return false } + +// Exporting to allow for easy test setups +type AlwaysFail struct{} + +func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { + return false, nil +} + +func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { + return false, nil +} + +func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return false, false, false, nil +} + +func (a AlwaysFail) AMPException() bool { + return false +} From f4b0a7cfc95aba6a27e9aaf06a57716fc2057c76 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 20 Aug 2020 14:19:37 -0400 Subject: [PATCH 171/603] Validate External Cache Host (#1422) * first draft * Little tweaks * Scott's review part 1 * Scott's review corrections part 2 * Scotts refactor * correction in config_test.go * Correction and refactor * Multiple return statements * Test case refactor Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon --- config/config.go | 36 +++++++++++++++++ config/config_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 9e6b1370128..e3b7d8ebda0 100755 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "errors" "fmt" "net/url" "reflect" @@ -111,6 +112,7 @@ func (cfg *Configuration) validate() configErrors { errs = cfg.CurrencyConverter.validate(errs) errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) + errs = cfg.ExtCacheURL.validate(errs) return errs } @@ -128,6 +130,40 @@ func (cfg *AuctionTimeouts) validate(errs configErrors) configErrors { return errs } +func (data *ExternalCache) validate(errs configErrors) configErrors { + if data.Host == "" && data.Path == "" { + // Both host and path can be blank. No further validation needed + return errs + } + + // Either host or path or both not empty, validate. + if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" { + return append(errs, errors.New("External cache Host and Path must both be specified")) + } + if strings.HasSuffix(data.Host, "/") { + return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' must not end with a path separator", data.Host))) + } + if strings.ContainsAny(data.Host, "://") { + return append(errs, errors.New(fmt.Sprintf("External cache Host must not specify a protocol. '%s'", data.Host))) + } + if !strings.HasPrefix(data.Path, "/") { + return append(errs, errors.New(fmt.Sprintf("External cache Path '%s' must begin with a path separator", data.Path))) + } + + urlObj, err := url.Parse("https://" + data.Host + data.Path) + if err != nil { + return append(errs, errors.New(fmt.Sprintf("External cache Path validation error: %s ", err.Error()))) + } + if urlObj.Host != data.Host { + return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' is invalid", data.Host))) + } + if urlObj.Path != data.Path { + return append(errs, errors.New("External cache Path is invalid")) + } + + return errs +} + // LimitAuctionTimeout returns the min of requested or cfg.MaxAuctionTimeout. // Both values treat "0" as "infinite". func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Duration { diff --git a/config/config_test.go b/config/config_test.go index 4774d9d6e46..3da3f72137b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -14,6 +14,91 @@ import ( "github.com/stretchr/testify/assert" ) +func TestExternalCacheURLValidate(t *testing.T) { + testCases := []struct { + desc string + data ExternalCache + expErrors int + }{ + { + desc: "With http://", + data: ExternalCache{Host: "http://www.google.com", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "Without http://", + data: ExternalCache{Host: "www.google.com", Path: "/path/v1"}, + expErrors: 0, + }, + { + desc: "No scheme but '//' prefix", + data: ExternalCache{Host: "//www.google.com", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "// appears twice", + data: ExternalCache{Host: "//www.google.com//", Path: "path/v1"}, + expErrors: 1, + }, + { + desc: "Host has an only // value", + data: ExternalCache{Host: "//", Path: "path/v1"}, + expErrors: 1, + }, + { + desc: "only scheme host, valid path", + data: ExternalCache{Host: "http://", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "No host, path only", + data: ExternalCache{Host: "", Path: "path/v1"}, + expErrors: 1, + }, + { + desc: "No host, nor path", + data: ExternalCache{Host: "", Path: ""}, + expErrors: 0, + }, + { + desc: "Invalid http at the end", + data: ExternalCache{Host: "www.google.com", Path: "http://"}, + expErrors: 1, + }, + { + desc: "Host has an unknown scheme", + data: ExternalCache{Host: "unknownscheme://host", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "Wrong colon side in scheme", + data: ExternalCache{Host: "http//:www.appnexus.com", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "Missing '/' in scheme", + data: ExternalCache{Host: "http:/www.appnexus.com", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "host with scheme, no path", + data: ExternalCache{Host: "http://www.appnexus.com", Path: ""}, + expErrors: 1, + }, + { + desc: "scheme, no host nor path", + data: ExternalCache{Host: "http://", Path: ""}, + expErrors: 1, + }, + } + for _, test := range testCases { + var errs configErrors + errs = test.data.validate(errs) + + assert.Equal(t, test.expErrors, len(errs), "Test case threw unexpected number of errors. Desc: %s errMsg = %v \n", test.desc, errs) + } +} + func TestDefaults(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -66,7 +151,7 @@ cache: query: uuid=%PBS_CACHE_UUID% external_cache: host: www.externalprebidcache.net - path: endpoints/cache + path: /endpoints/cache http_client: max_connections_per_host: 10 max_idle_connections: 500 @@ -223,7 +308,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "cache.host", cfg.CacheURL.Host, "prebidcache.net") cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%") cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net") - cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "endpoints/cache") + cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "/endpoints/cache") cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10) cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500) cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20) From 80d557ece7ae79413755db8021e9fd0559b1954c Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 20 Aug 2020 15:36:33 -0400 Subject: [PATCH 172/603] Fixes bug (#1448) * Fixes bug * shortens list --- exchange/exchange.go | 1 + exchange/exchange_test.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/exchange/exchange.go b/exchange/exchange.go index 53f4a7a3e1f..e465a78389b 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -76,6 +76,7 @@ type bidResponseWrapper struct { func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter) Exchange { e := new(exchange) + e.eeaCountries = make(map[string]struct{}, len(cfg.GDPR.EEACountries)) var s struct{} for _, c := range cfg.GDPR.EEACountries { e.eeaCountries[c] = s diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index aad448f397f..a6f69f70c59 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -48,6 +48,9 @@ func TestNewExchange(t *testing.T) { ExpectedTimeMillis: 20, }, Adapters: blankAdapterConfig(openrtb_ext.BidderList()), + GDPR: config.GDPR{ + EEACountries: []string{"FIN", "FRA", "GUF"}, + }, } currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) From d66338035f232aef73099ae1c257628142c62e2c Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Mon, 24 Aug 2020 13:43:02 -0700 Subject: [PATCH 173/603] Added adpod_id to request extension (#1444) * Added adpod_id to request -> ext -> appnexus and modified requests splitting based on pod * Unit test fix * Unit test fix * Minor unit test fixes * Code refactoring * Minor code and unit tests refactoring * Unit tests refactoring Co-authored-by: Veronika Solovei --- adapters/appnexus/appnexus.go | 62 ++++- adapters/appnexus/appnexus_test.go | 229 ++++++++++++++++++ .../video/simple-video.json | 132 ---------- 3 files changed, 283 insertions(+), 140 deletions(-) delete mode 100644 adapters/appnexus/appnexusplatformtest/video/simple-video.json diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 9bec9bf1e3b..334817ebca7 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/rand" "net/http" "strconv" "strings" @@ -95,10 +96,11 @@ type appnexusBidExt struct { } type appnexusReqExtAppnexus struct { - IncludeBrandCategory *bool `json:"include_brand_category,omitempty"` - BrandCategoryUniqueness *bool `json:"brand_category_uniqueness,omitempty"` - IsAMP int `json:"is_amp,omitempty"` - HeaderBiddingSource int `json:"hb_source,omitempty"` + IncludeBrandCategory *bool `json:"include_brand_category,omitempty"` + BrandCategoryUniqueness *bool `json:"brand_category_uniqueness,omitempty"` + IsAMP int `json:"is_amp,omitempty"` + HeaderBiddingSource int `json:"hb_source,omitempty"` + AdPodId string `json:"adpod_id,omitempty"` } // Full request extension including appnexus extension object @@ -354,14 +356,56 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada } reqExt.Appnexus.IsAMP = isAMP reqExt.Appnexus.HeaderBiddingSource = a.hbSource + isVIDEO + + imps := request.Imp + + // For long form requests adpod_id must be sent downstream. + // Adpod id is a unique identifier for pod + // All impressions in the same pod must have the same pod id in request extension + // For this all impressions in request should belong to the same pod + // If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same + if isVIDEO == 1 { + podImps := groupByPods(imps) + + requests := make([]*adapters.RequestData, 0, len(podImps)) + for _, podImps := range podImps { + reqExt.Appnexus.AdPodId = generatePodId() + + reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs) + requests = append(requests, reqs...) + errs = append(errs, errors...) + } + return requests, errs + } + + return splitRequests(imps, request, reqExt, thisURI, errs) +} + +func generatePodId() string { + val := rand.Int63() + return fmt.Sprint(val) +} + +func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) { + // find number of pods in response + podImps := make(map[string][]openrtb.Imp) + for _, imp := range imps { + pod := strings.Split(imp.ID, "_")[0] + podImps[pod] = append(podImps[pod], imp) + } + return podImps +} + +func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appnexusReqExt, errs []error) { var err error - request.Ext, err = json.Marshal(reqExt) + request.Ext, err = json.Marshal(requestExtension) if err != nil { errs = append(errs, err) - return nil, errs } +} + +func splitRequests(imps []openrtb.Imp, request *openrtb.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) { - imps := request.Imp // Initial capacity for future array of requests, memory optimization. // Let's say there are 35 impressions and limit impressions per request equals to 10. // In this case we need to create 4 requests with 10, 10, 10 and 5 impressions. @@ -375,6 +419,8 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") + marshalAndSetRequestExt(request, requestExtension, errs) + for impsLeft { endInd := startInd + maxImpsPerReq @@ -393,7 +439,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada resArr = append(resArr, &adapters.RequestData{ Method: "POST", - Uri: thisURI, + Uri: uri, Body: reqJSON, Headers: headers, }) diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index bf49374940a..26380406624 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -4,9 +4,11 @@ import ( "bytes" "context" "encoding/json" + "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "net/http/httptest" + "regexp" "testing" "time" @@ -38,6 +40,233 @@ func TestMemberQueryParam(t *testing.T) { } } +func TestVideoSinglePod(t *testing.T) { + var a AppNexusAdapter + a.URI = "http://test.com/openrtb2" + a.hbSource = 5 + + var reqInfo adapters.ExtraRequestInfo + reqInfo.PbsEntryPoint = "video" + + var req openrtb.BidRequest + req.ID = "test_id" + + reqExt := `{"prebid":{}}` + impExt := `{"bidder":{"placementId":123}}` + req.Ext = []byte(reqExt) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + + result, err := a.MakeRequests(&req, &reqInfo) + + assert.Empty(t, err, "Errors array should be empty") + assert.Len(t, result, 1, "Only one request should be returned") + + var error error + var reqData *openrtb.BidRequest + error = json.Unmarshal(result[0].Body, &reqData) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt *appnexusReqExt + error = json.Unmarshal(reqData.Ext, &reqDataExt) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + regMatch, matchErr := regexp.Match(`[0-9]19`, []byte(reqDataExt.Appnexus.AdPodId)) + assert.NoError(t, matchErr, "Regex match error should be nil") + assert.True(t, regMatch, "AdPod id doesn't present in Appnexus extension or has incorrect format") +} + +func TestVideoSinglePodManyImps(t *testing.T) { + var a AppNexusAdapter + a.URI = "http://test.com/openrtb2" + a.hbSource = 5 + + var reqInfo adapters.ExtraRequestInfo + reqInfo.PbsEntryPoint = "video" + + var req openrtb.BidRequest + req.ID = "test_id" + + reqExt := `{"prebid":{}}` + impExt := `{"bidder":{"placementId":123}}` + req.Ext = []byte(reqExt) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_14", Ext: []byte(impExt)}) + + res, err := a.MakeRequests(&req, &reqInfo) + + assert.Empty(t, err, "Errors array should be empty") + assert.Len(t, res, 2, "Two requests should be returned") + + var error error + var reqData1 *openrtb.BidRequest + error = json.Unmarshal(res[0].Body, &reqData1) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt1 *appnexusReqExt + error = json.Unmarshal(reqData1.Ext, &reqDataExt1) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + adPodId1 := reqDataExt1.Appnexus.AdPodId + + var reqData2 *openrtb.BidRequest + error = json.Unmarshal(res[1].Body, &reqData2) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt2 *appnexusReqExt + error = json.Unmarshal(reqData2.Ext, &reqDataExt2) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + adPodId2 := reqDataExt2.Appnexus.AdPodId + + assert.Equal(t, adPodId1, adPodId2, "AdPod id is not the same for the same pod") +} + +func TestVideoTwoPods(t *testing.T) { + var a AppNexusAdapter + a.URI = "http://test.com/openrtb2" + a.hbSource = 5 + + var reqInfo adapters.ExtraRequestInfo + reqInfo.PbsEntryPoint = "video" + + var req openrtb.BidRequest + req.ID = "test_id" + + reqExt := `{"prebid":{}}` + impExt := `{"bidder":{"placementId":123}}` + req.Ext = []byte(reqExt) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) + + res, err := a.MakeRequests(&req, &reqInfo) + + assert.Empty(t, err, "Errors array should be empty") + assert.Len(t, res, 2, "Two request should be returned") + + var error error + var reqData1 *openrtb.BidRequest + error = json.Unmarshal(res[0].Body, &reqData1) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt1 *appnexusReqExt + error = json.Unmarshal(reqData1.Ext, &reqDataExt1) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + adPodId1 := reqDataExt1.Appnexus.AdPodId + + var reqData2 *openrtb.BidRequest + error = json.Unmarshal(res[1].Body, &reqData2) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt2 *appnexusReqExt + error = json.Unmarshal(reqData2.Ext, &reqDataExt2) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + adPodId2 := reqDataExt2.Appnexus.AdPodId + + assert.NotEqual(t, adPodId1, adPodId2, "AdPod id should be different for different pods") +} + +func TestVideoTwoPodsManyImps(t *testing.T) { + var a AppNexusAdapter + a.URI = "http://test.com/openrtb2" + a.hbSource = 5 + + var reqInfo adapters.ExtraRequestInfo + reqInfo.PbsEntryPoint = "video" + + var req openrtb.BidRequest + req.ID = "test_id" + + reqExt := `{"prebid":{}}` + impExt := `{"bidder":{"placementId":123}}` + req.Ext = []byte(reqExt) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb.Imp{ID: "2_14", Ext: []byte(impExt)}) + + res, err := a.MakeRequests(&req, &reqInfo) + + assert.Empty(t, err, "Errors array should be empty") + assert.Len(t, res, 3, "Three requests should be returned") + + var error error + var reqData1 *openrtb.BidRequest + error = json.Unmarshal(res[0].Body, &reqData1) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt1 *appnexusReqExt + error = json.Unmarshal(reqData1.Ext, &reqDataExt1) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + var reqData2 *openrtb.BidRequest + error = json.Unmarshal(res[1].Body, &reqData2) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt2 *appnexusReqExt + error = json.Unmarshal(reqData2.Ext, &reqDataExt2) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + var reqData3 *openrtb.BidRequest + error = json.Unmarshal(res[2].Body, &reqData3) + assert.NoError(t, error, "Response body unmarshalling error should be nil") + + var reqDataExt3 *appnexusReqExt + error = json.Unmarshal(reqData3.Ext, &reqDataExt3) + assert.NoError(t, error, "Response ext unmarshalling error should be nil") + + adPodId1 := reqDataExt1.Appnexus.AdPodId + adPodId2 := reqDataExt2.Appnexus.AdPodId + adPodId3 := reqDataExt3.Appnexus.AdPodId + + podIds := make(map[string]int) + podIds[adPodId1] = podIds[adPodId1] + 1 + podIds[adPodId2] = podIds[adPodId2] + 1 + podIds[adPodId3] = podIds[adPodId3] + 1 + + assert.Len(t, podIds, 2, "Incorrect number of unique pod ids") +} + // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we // clean up the existing code and make everything openrtb. diff --git a/adapters/appnexus/appnexusplatformtest/video/simple-video.json b/adapters/appnexus/appnexusplatformtest/video/simple-video.json deleted file mode 100644 index 7ee192be2c1..00000000000 --- a/adapters/appnexus/appnexusplatformtest/video/simple-video.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 15, - "maxduration": 30, - "protocols": [2, 3, 5, 6, 7, 8], - "w": 940, - "h": 560 - }, - "ext": { - "bidder": { - "placement_id": 1 - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://ib.adnxs.com/openrtb2", - "body": { - "id": "test-request-id", - "ext": { - "appnexus": { - "hb_source": 9 - }, - "prebid": {} - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 15, - "maxduration": 30, - "protocols": [2, 3, 5, 6, 7, 8], - "w": 940, - "h": 560 - }, - "ext": { - "appnexus": { - "placement_id": 1 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["appnexus.com"], - "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300, - "cat": ["IAB9-1"], - "ext": { - "appnexus": { - "brand_id": 9, - "brand_category_id": 9, - "auction_id": 8189378542222915032, - "bid_ad_type": 1, - "bidder_id": 2, - "ranking_price": 0.000000, - "deal_priority": 5 - } - } - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["appnexus.com"], - "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250, - "cat": ["IAB5-3"], - "ext": { - "appnexus": { - "brand_id": 9, - "brand_category_id": 9, - "auction_id": 8189378542222915032, - "bid_ad_type": 1, - "bidder_id": 2, - "ranking_price": 0.000000, - "deal_priority": 5 - } - } - }, - "type": "video" - } - ] - } - ] - } \ No newline at end of file From 30ef8581806f5957c4417f05cd305e709d53a92e Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Mon, 24 Aug 2020 23:49:06 +0300 Subject: [PATCH 174/603] Adform adapter: additional targeting params added (#1424) --- adapters/adform/adform.go | 21 +++++++++++++++++++++ adapters/adform/adform_test.go | 30 ++++++++++++++++++++++-------- adapters/adform/params_test.go | 8 ++++++++ openrtb_ext/imp_adform.go | 11 +++++++---- static/bidder-params/adform.json | 14 ++++++++++++++ 5 files changed, 72 insertions(+), 12 deletions(-) diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 69f1c12f073..5881f4ab86e 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -43,6 +43,7 @@ type adformRequest struct { digitrust *adformDigitrust currency string eids string + url string } type adformDigitrust struct { @@ -61,6 +62,9 @@ type adformAdUnit struct { PriceType string `json:"priceType,omitempty"` KeyValues string `json:"mkv,omitempty"` KeyWords string `json:"mkw,omitempty"` + CDims string `json:"cdims,omitempty"` + MinPrice float64 `json:"minp,omitempty"` + Url string `json:"url,omitempty"` bidId string adUnitCode string @@ -284,6 +288,10 @@ func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { parameters.Add("eids", r.eids) } + if r.url != "" { + parameters.Add("url", r.url) + } + URL := *a.URL URL.RawQuery = parameters.Encode() @@ -302,6 +310,12 @@ func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { if adUnit.KeyWords != "" { buffer.WriteString(fmt.Sprintf("&mkw=%s", adUnit.KeyWords)) } + if adUnit.CDims != "" { + buffer.WriteString(fmt.Sprintf("&cdims=%s", adUnit.CDims)) + } + if adUnit.MinPrice > 0 { + buffer.WriteString(fmt.Sprintf("&minp=%.2f", adUnit.MinPrice)) + } adUnitsParams = append(adUnitsParams, base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buffer.Bytes())) } @@ -407,6 +421,8 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro adUnits := make([]*adformAdUnit, 0, len(request.Imp)) errors := make([]error, 0, len(request.Imp)) secure := false + url := "" + for _, imp := range request.Imp { params, _, _, err := jsonparser.Get(imp.Ext, "bidder") if err != nil { @@ -441,6 +457,10 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro secure = true } + if url == "" { + url = adformAdUnit.Url + } + adformAdUnit.bidId = imp.ID adformAdUnit.adUnitCode = imp.ID adUnits = append(adUnits, &adformAdUnit) @@ -520,6 +540,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro digitrust: digitrust, currency: requestCurrency, eids: eids, + url: url, }, errors } diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 2fca7d1722d..f227776207d 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -35,6 +35,9 @@ type aTagInfo struct { keyValues string keyWords string code string + cdims string + url string + minp float64 price float64 content string @@ -320,9 +323,9 @@ func createTestData(secure bool) aBidInfo { tid: "transaction-id", buyerUID: "user-id", tags: []aTagInfo{ - {mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"}, - {mid: 32345, priceType: "net", code: "code2"}, // no bid for ad unit - {mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2"}, + {mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", cdims: "300x300,400x200", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"}, + {mid: 32345, priceType: "net", code: "code2", minp: 23.1, cdims: "300x200"}, // no bid for ad unit + {mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2", url: "https://adform.com?a=b"}, }, secure: secure, currency: "EUR", @@ -519,11 +522,22 @@ func getUserExt() []byte { } func formatAdUnitJson(tag aTagInfo) string { - return fmt.Sprintf("{ \"mid\": %d%s%s%s}", + return fmt.Sprintf("{ \"mid\": %d%s%s%s%s%s%s}", tag.mid, formatAdUnitParam("priceType", tag.priceType), formatAdUnitParam("mkv", tag.keyValues), - formatAdUnitParam("mkw", tag.keyWords)) + formatAdUnitParam("mkw", tag.keyWords), + formatAdUnitParam("cdims", tag.cdims), + formatAdUnitParam("url", tag.url), + formatDemicalAdUnitParam("minp", tag.minp)) +} + +func formatDemicalAdUnitParam(fieldName string, fieldValue float64) string { + if fieldValue > 0 { + return fmt.Sprintf(", \"%s\": %.2f", fieldName, fieldValue) + } + + return "" } func formatAdUnitParam(fieldName string, fieldValue string) string { @@ -547,10 +561,10 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo var midsWithCurrency = "" var queryString = "" if isOpenRtb { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9RVVS&bWlkPTMyMzQ2JnJjdXI9RVVS" - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS" + queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency } else { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9VVNE&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency } diff --git a/adapters/adform/params_test.go b/adapters/adform/params_test.go index ae0a02b6a97..b392463f426 100644 --- a/adapters/adform/params_test.go +++ b/adapters/adform/params_test.go @@ -48,6 +48,10 @@ var validParams = []string{ `{"mid":"123","mkv":"color:"}`, `{"mid":"123","mkw":"green,male"}`, `{"mid":"123","mkv":" ","mkw":" "}`, + `{"mid":"123","cdims":"500x300,400x200","mkw":" "}`, + `{"mid":"123","cdims":"500x300","mkv":" ","mkw":" "}`, + `{"mid":"123","minp":2.1}`, + `{"mid":"123","url":"https://adform.com/page"}`, } var invalidParams = []string{ @@ -66,4 +70,8 @@ var invalidParams = []string{ `{"mid":"123","mkv":"color:blue,l&ngth:350"}`, `{"mid":"123","mkv":"color::blue"}`, `{"mid":"123","mkw":"fem&le"}`, + `{"mid":"123","minp":"2.1"}`, + `{"mid":"123","cdims":"500x300:400:200","mkw":" "}`, + `{"mid":"123","cdims":"500x300,400:200","mkv":" ","mkw":" "}`, + `{"mid":"123","url":10}`, } diff --git a/openrtb_ext/imp_adform.go b/openrtb_ext/imp_adform.go index 3e7c1a7261e..3206ece7c9b 100644 --- a/openrtb_ext/imp_adform.go +++ b/openrtb_ext/imp_adform.go @@ -1,8 +1,11 @@ package openrtb_ext type ExtImpAdform struct { - MasterTagId string `json:"mid"` - PriceType string `json:"priceType,omitempty"` - KeyValues string `json:"mkv,omitempty"` - KeyWords string `json:"mkw,omitempty"` + MasterTagId string `json:"mid"` + PriceType string `json:"priceType,omitempty"` + KeyValues string `json:"mkv,omitempty"` + KeyWords string `json:"mkw,omitempty"` + CDims string `json:"cdims,omitempty"` + MinPrice float64 `json:"minp,omitempty"` + Url string `json:"url,omitempty"` } diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json index 67f09623ee4..f0b8c7a6be0 100644 --- a/static/bidder-params/adform.json +++ b/static/bidder-params/adform.json @@ -22,6 +22,20 @@ "type": "string", "description": "Comma-separated keywords. Forbidden symbols: &.", "pattern": "^[^&]*$" + }, + "cdims": { + "type": "string", + "description": "Comma-separated creative dimentions.", + "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$" + }, + "minp": { + "type": "number", + "description": "The minimum CPM price.", + "minimum": 0 + }, + "url": { + "type": "string", + "description": "Custom URL for targeting." } }, "required": ["mid"] From 9dbd0083704315ac80721da30823753ee146bf19 Mon Sep 17 00:00:00 2001 From: Rob Hazan Date: Mon, 24 Aug 2020 17:28:42 -0400 Subject: [PATCH 175/603] Fix minor error message spelling mistake "vastml" -> "vastxml" (#1455) --- .../openrtb2/sample-requests/invalid-whole/cache-nothing.json | 2 +- openrtb_ext/request.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json index d4b875498ae..f256e4eb34c 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json @@ -1,5 +1,5 @@ { - "message": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastml\" properties\n", + "message": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n", "requestPayload": { "id": "some-request-id", "site": { diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 23daaf0f76e..d6edf47f939 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -67,7 +67,7 @@ func (ert *ExtRequestPrebidCache) UnmarshalJSON(b []byte) error { } if proxy.Bids == nil && proxy.VastXML == nil { - return errors.New(`request.ext.prebid.cache requires one of the "bids" or "vastml" properties`) + return errors.New(`request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) } *ert = ExtRequestPrebidCache(proxy) From 055ab8062c29038a3ac451e119f07d002ff57498 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Tue, 25 Aug 2020 08:37:04 -0700 Subject: [PATCH 176/603] Fixing comment for usage of deal priority field (#1451) --- adapters/bidder.go | 2 +- exchange/bidder.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/bidder.go b/adapters/bidder.go index 627caf67344..41218aa6a2f 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -108,7 +108,7 @@ func NewBidderResponse() *BidderResponse { // TypedBid.Bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response. // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response. -// TypedBid.DealPriority will become "response.seatbid[i].bid.dealPriority" in the final OpenRTB response. +// TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. type TypedBid struct { Bid *openrtb.Bid BidType openrtb_ext.BidType diff --git a/exchange/bidder.go b/exchange/bidder.go index decad8ccf2f..5924e39b031 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -57,7 +57,7 @@ type adaptedBidder interface { // pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange. // pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video. -// pbsOrtbBid.dealPriority will become "response.seatbid[i].bid.dealPriority" in the final OpenRTB response. +// pbsOrtbBid.dealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. type pbsOrtbBid struct { bid *openrtb.Bid bidType openrtb_ext.BidType From e96b980d391ae89ba1a9631f5921a7db6aa24ba8 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 25 Aug 2020 12:43:06 -0400 Subject: [PATCH 177/603] moving docs to website repo (#1443) --- README.md | 14 +- docs/bidders/adtarget.md | 5 - docs/bidders/appnexus.md | 45 - docs/bidders/audienceNetwork.md | 8 - docs/bidders/avocet.md | 5 - docs/bidders/beachfront.md | 13 - docs/bidders/emx_digital.md | 10 - docs/bidders/kidoz.md | 9 - docs/bidders/openx.md | 65 -- docs/bidders/pubmatic.md | 33 - docs/bidders/pubnative.md | 62 -- docs/bidders/rubicon.md | 7 - docs/bidders/smaato.md | 42 - docs/bidders/smartAdserver.md | 59 -- docs/bidders/smartrtb.md | 39 - docs/bidders/sovrn.md | 3 - docs/bidders/tappx.md | 13 - ...Server Event Notifications - Tech Spec.pdf | Bin 89983 -> 0 bytes docs/developers/add-new-analytics-module.md | 33 - docs/developers/add-new-bidder.md | 117 --- docs/developers/cookie-syncs.md | 30 - docs/developers/currency-converter.md | 56 -- docs/developers/default-request.md | 44 - docs/developers/features.md | 12 + docs/developers/gdpr.md | 31 - docs/developers/stored-requests.md | 4 +- docs/endpoints.md | 1 + docs/endpoints/bidders/params.md | 24 - docs/endpoints/cookieSync.md | 55 -- docs/endpoints/currency_rates.md | 111 --- docs/endpoints/info/bidders.md | 23 - docs/endpoints/info/bidders/bidderName.md | 43 - docs/endpoints/openrtb2/amp.md | 127 --- docs/endpoints/openrtb2/auction.md | 789 ------------------ docs/endpoints/setuid.md | 26 - docs/endpoints/status.md | 9 - 36 files changed, 22 insertions(+), 1945 deletions(-) delete mode 100644 docs/bidders/adtarget.md delete mode 100644 docs/bidders/appnexus.md delete mode 100644 docs/bidders/audienceNetwork.md delete mode 100644 docs/bidders/avocet.md delete mode 100644 docs/bidders/beachfront.md delete mode 100644 docs/bidders/emx_digital.md delete mode 100644 docs/bidders/kidoz.md delete mode 100644 docs/bidders/openx.md delete mode 100644 docs/bidders/pubmatic.md delete mode 100644 docs/bidders/pubnative.md delete mode 100644 docs/bidders/rubicon.md delete mode 100644 docs/bidders/smaato.md delete mode 100644 docs/bidders/smartAdserver.md delete mode 100644 docs/bidders/smartrtb.md delete mode 100644 docs/bidders/sovrn.md delete mode 100644 docs/bidders/tappx.md delete mode 100644 docs/developers/Prebid Server Event Notifications - Tech Spec.pdf delete mode 100644 docs/developers/add-new-analytics-module.md delete mode 100644 docs/developers/add-new-bidder.md delete mode 100644 docs/developers/cookie-syncs.md delete mode 100644 docs/developers/currency-converter.md delete mode 100644 docs/developers/default-request.md create mode 100644 docs/developers/features.md delete mode 100644 docs/developers/gdpr.md create mode 100644 docs/endpoints.md delete mode 100644 docs/endpoints/bidders/params.md delete mode 100644 docs/endpoints/cookieSync.md delete mode 100644 docs/endpoints/currency_rates.md delete mode 100644 docs/endpoints/info/bidders.md delete mode 100644 docs/endpoints/info/bidders/bidderName.md delete mode 100644 docs/endpoints/openrtb2/amp.md delete mode 100644 docs/endpoints/openrtb2/auction.md delete mode 100644 docs/endpoints/setuid.md delete mode 100644 docs/endpoints/status.md diff --git a/README.md b/README.md index b69e7e76db4..673c2b1bdeb 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ It is managed by [Prebid.org](http://prebid.org/overview/what-is-prebid-org.html and upholds the principles from the [Prebid Code of Conduct](http://prebid.org/wrapper_code_of_conduct.html). This project does not support the same set of Bidders as Prebid.js, although there is overlap. -The current set can be found in the [adapters](./adapters) package. If you don't see the one you want, feel free to [contribute it](docs/developers/add-new-bidder.md). +The current set can be found in the [adapters](./adapters) package. If you don't see the one you want, feel free to [contribute it](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html). For more information, see: -- [What is Prebid?](http://prebid.org/overview/intro.html) -- [Getting started with Prebid Server](http://prebid.org/dev-docs/get-started-with-prebid-server.html) -- [Current Bidders](http://prebid.org/dev-docs/prebid-server-bidders.html) +- [What is Prebid?](https://prebid.org/overview/intro.html) +- [Prebid Server Overview](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html) +- [Current Bidders](http://prebid.org/dev-docs/pbs-bidders.html) ## Installation @@ -45,14 +45,12 @@ go build . ``` Load the landing page in your browser at `http://localhost:8000/`. -For the full API reference, see [docs/endpoints](docs/endpoints) +For the full API reference, see [the endpoint documentation](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) ## Contributing -Want to [add an adapter](docs/developers/add-new-bidder.md)? Found a bug? Great! -This project is in its infancy, and many things can be improved. - +Want to [add an adapter](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html)? Found a bug? Great! Report bugs, request features, and suggest improvements [on Github](https://github.com/prebid/prebid-server/issues). diff --git a/docs/bidders/adtarget.md b/docs/bidders/adtarget.md deleted file mode 100644 index b658a728a2b..00000000000 --- a/docs/bidders/adtarget.md +++ /dev/null @@ -1,5 +0,0 @@ -# Adtarget bidder - -To use the Adtarget bidder you will need an aid from an exchange account on [https://console.adtarget.com.tr](adtarget.com.tr). - -For further information, please contact kamil@adtarget.com.tr \ No newline at end of file diff --git a/docs/bidders/appnexus.md b/docs/bidders/appnexus.md deleted file mode 100644 index e4032313f25..00000000000 --- a/docs/bidders/appnexus.md +++ /dev/null @@ -1,45 +0,0 @@ -# Appnexus Bidder - -## Using Keywords - -The `keywords` [bidder param](../../static/bidder-params/appnexus.json) will only work if -it's enabled for your Account with Appnexus. - -**This permission is _distinct_ from the keywords feature used by Prebid.js.** - -If you want to enable Appnexus keywords, contact your account manager. - -## Display Manager Version - -The AppNexus endpoint expects `imp.displaymanagerver` to be populated for mobile app sources -requests, however not all SDKs will populate this field. If the `imp.displaymanagerver` field -is not supplied for an `imp`, but `request.app.ext.prebid.source` -and `request.app.ext.prebid.version` are supplied, the adapter will fill in a value for -`diplaymanagerver`. It will concatenate the two `app` fields as `-` fo fill in -the empty `displaymanagerver` before sending the request to AppNexus. - -## Test Request - -The following test parameters can be used to verify that Prebid Server is working properly with the -Appnexus adapter. This example includes an `imp` object with an Appnexus test placement ID and sizes -that would match with the test creative. - -``` - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "appnexus": { - "placementId": 13144370 - } - } - }] -``` \ No newline at end of file diff --git a/docs/bidders/audienceNetwork.md b/docs/bidders/audienceNetwork.md deleted file mode 100644 index d55e8218a81..00000000000 --- a/docs/bidders/audienceNetwork.md +++ /dev/null @@ -1,8 +0,0 @@ -# Audience Network Bidder - -## Mobile Bids - -Audience Network will not bid on requests made from device simulators. -When testing for Mobile bids, you must make bid requests using a real device. - -**Note:** Audience Network is disabled by default. Please enable it in the app config if you wish to use it. Make sure you provide the partnerID for the auctions to run correctly. \ No newline at end of file diff --git a/docs/bidders/avocet.md b/docs/bidders/avocet.md deleted file mode 100644 index 6aa67391af4..00000000000 --- a/docs/bidders/avocet.md +++ /dev/null @@ -1,5 +0,0 @@ -# Avocet Bidder - -Please contact Avocet at info@avocet.io if you would like to get started selling inventory via the Avocet platform. - -**Note:** Avocet is disabled by default. Please enable it in the app config if you wish to use it. This can be done by setting `adapters.avocet.disabled` to `false` and by setting `adapters.avocet.endpoint` to a valid Avocet endpoint url. \ No newline at end of file diff --git a/docs/bidders/beachfront.md b/docs/bidders/beachfront.md deleted file mode 100644 index ecd7a8f95d1..00000000000 --- a/docs/bidders/beachfront.md +++ /dev/null @@ -1,13 +0,0 @@ -# Beachfront bidder - -To use the beachfront bidder you will need an appId (Exchange Id) from an exchange -account on [platform.beachfront.io](https://platform.beachfront.io). - -For further information, please contact adops@beachfront.com. - -As seen in the JSON response from \{your PBS server\}\/bidder\/params [(example)](https://prebid.adnxs.com/pbs/v1/bidders/params), the beachfront bidder can take either an "appId" parameter, or an "appIds" parameter. If the request is for one media type, the appId parameter should be used with the value of the Exchange Id on the Beachfront platform. - -The appIds parameter is for requesting a mix of banner and video. It has two parameters, "banner", and "video" for the appIds of two appropriately configured exchanges on the platform. The appIds parameter can be sent with just one of its two parameters and it will behave like the appId parameter. - -If the request includes an appId configured for a video response, the videoResponseType parameter can be defined as "nurl", "adm" or "both". These will apply to all video returned. If it is not defined, the response type will be a nurl. The definitions for "nurl" vs. "adm" are here: (https://github.com/mxmCherry/openrtb/blob/master/openrtb2/bid.go). - diff --git a/docs/bidders/emx_digital.md b/docs/bidders/emx_digital.md deleted file mode 100644 index 0ba81d59fea..00000000000 --- a/docs/bidders/emx_digital.md +++ /dev/null @@ -1,10 +0,0 @@ -# EMX Digital Bidder - -[EMX Digital](https://emxdigital.com/) supports the following parameters to be present in the `ext` object of impression requests: - -- "tagid" type string - Required. Unique inventory ID. -- "bidfloor" type string - Optional. The minimum acceptable bid for the unit, in CPM and USD. - -To use this bidder you will need an account and a valid tagid from our exchange. - -For further information, please contact your Account Manager or adops@emxdigital.com. diff --git a/docs/bidders/kidoz.md b/docs/bidders/kidoz.md deleted file mode 100644 index 433dd71c2ca..00000000000 --- a/docs/bidders/kidoz.md +++ /dev/null @@ -1,9 +0,0 @@ -# Kidoz Bidder - -Kidoz is exclusively for Mobile app COPPA compatible ads, 100% kid relevant and appropriate. - -In order for a company to receive bids from Kidoz, they must first open a publisher account at Kidoz.net -(https://accounts.kidoz.net/publishers/register) and accept the Kidoz Terms and Conditions and Privacy Policy. -Kidoz publishers must confirm that all of their content properties are COPPA and GDPR compliant and perform no monitoring -or tracking of U13 users in their operations. New publishers are provided a Publisher ID and AccessToken, this can also -be used to login to their dashboard at the Kidoz.net portal to monitor their account activity. diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md deleted file mode 100644 index 54a0a5b1e72..00000000000 --- a/docs/bidders/openx.md +++ /dev/null @@ -1,65 +0,0 @@ -# OpenX Bidder - -OpenX supports the following parameters: - -| property | type | required? | description | example | -|----------|------|-----------|-------------|---------| -| unit | string | required | The ad unit id | "10092842" | -| delDomain | string | required\* | The delivery domain for the customer | "sademo-d.openx.net" | -| platform | uuid | required\* | The platform id for the customer | "a3aece0c-9e80-4316-8deb-faf804779bd1" | -| customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | -| customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | - -\* At least one of `delDomain` or `platform` parameters is required. - -If you have any questions regarding setting up, please reach out to your account manager or - - -## Test Request - -### App Impression Object -``` -{ - "id": "test-impression-id", - "banner": { - "format": [ - { - "w": 480, - "h": 300 - }, - { - "w": 480, - "h": 320 - } - ] - }, - "ext": { - "openx": { - "delDomain": "mobile-d.openx.net", - "unit": "541028953" - } - } -} -``` - - -### Web -``` -{ - "id": "div1", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, - "ext": { - "openx": { - "unit": "540949380", - "delDomain": "sademo-d.openx.net" - }, - } -} -``` diff --git a/docs/bidders/pubmatic.md b/docs/bidders/pubmatic.md deleted file mode 100644 index 610108b2e07..00000000000 --- a/docs/bidders/pubmatic.md +++ /dev/null @@ -1,33 +0,0 @@ -# PubMatic Bidder - -## Test Request - -The following test parameters can be used to verify that Prebid Server is working properly with the -PubMatic adapter. This example includes an `imp` object with an PubMatic test publisher ID, ad slot, -and sizes that would match with the test creative. - -``` -"imp":[ - { - "id":“"some-impression-id”, - "banner":{ - "format":[ - { - "w":300, - "h":250 - }, - { - "w":300, - "h":600 - } - ] - }, - "ext":{ - "pubmatic":{ - "publisherId":“156276”, - "adSlot":"pubmatic_test" - } - } - } - ] -``` \ No newline at end of file diff --git a/docs/bidders/pubnative.md b/docs/bidders/pubnative.md deleted file mode 100644 index a25cafe0cd5..00000000000 --- a/docs/bidders/pubnative.md +++ /dev/null @@ -1,62 +0,0 @@ -# Pubnative Bidder - -## Prerequisite -Before adding PubNative as a new bidder, there are 3 prerequisites: -- As a Publisher, you need to have Prebid Mobile SDK integrated. -- You need a configured Prebid Server (either self-hosted or hosted by 3rd party). -- You need to be integrated with Ad Server SDK (e.g. Mopub) or internal product which communicates with Prebid Mobile SDK. - -Please see [documentation](https://developers.pubnative.net/docs/prebid-adding-pubnative-as-a-bidder) for more info. - -## Configuration - -- bidder should be always set to "pubnative" (`imp.ext.pubnative`) -- zone_id (int) should be always set to 1, unless special use case agreed with our account manager. (`imp.ext.pubnative.zone_id`) -- app_auth_token (string) is unique per publisher app. Please contact our account manager to obtain yours. (`imp.ext.pubnative.app_auth_token`) - -An example is illustrated in a section below. - -## Testing - -Please consult with our Account Manager for testing. -We need to confirm that your ad request is correctly received by our system. - -The following test parameters can be used to verify that Prebid Server is working properly with the -Pubnative adapter. - -The following json can be used to do a request to prebid server for verifying its integration with Pubnative adapter. - -```json -{ - "id": "some-impression-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "pubnative": { - "zone_id": 1, - "app_auth_token": "b620e282f3c74787beedda34336a4821" - } - } - } - ], - "device": { - "os": "android", - "h": 700, - "w": 375 - }, - "tmax": 500, - "test": 1 -} -``` \ No newline at end of file diff --git a/docs/bidders/rubicon.md b/docs/bidders/rubicon.md deleted file mode 100644 index ea376da427d..00000000000 --- a/docs/bidders/rubicon.md +++ /dev/null @@ -1,7 +0,0 @@ -# Rubicon Bidder - -Please contact your Rubicon Project account manager or globalsupport@rubiconproject.com to get set up with a login and cookie-sync URL to run your own Prebid Server. You will be given instructions, including the available endpoints. - -**Note:** Rubicon is disabled by default. Please enable it in the app config if you wish to use it. Make sure you provide the correct cookie-sync URL in order for cookie-syncs to work properly. - -[Rubicon Project Prebid.js test parameters](https://github.com/prebid/Prebid.js/blob/master/modules/rubiconBidAdapter.md) will work for server as well. diff --git a/docs/bidders/smaato.md b/docs/bidders/smaato.md deleted file mode 100644 index 881f8f2ab54..00000000000 --- a/docs/bidders/smaato.md +++ /dev/null @@ -1,42 +0,0 @@ - -# Smaato Bidder - -``` -Module Name: Smaato Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@smaato.com -``` - -### Description - -Please contact Smaato Support or prebid@smaato.com to get set up with a publisherId and adspaceId. - -### Test Parameters: - -Following example includes sample `imp` object with publisherId and adSlot which can be used to test Smaato Adapter - -``` -"imp":[ - { - "id":“1C86242D-9535-47D6-9576-7B1FE87F282C, - "banner":{ - "format":[ - { - "w":300, - "h":50 - }, - { - "w":300, - "h":250 - } - ] - }, - "ext":{ - "smaato":{ - "publisherId":"100042525", - "adspaceId":"130563103" - } - } - } - ] -``` diff --git a/docs/bidders/smartAdserver.md b/docs/bidders/smartAdserver.md deleted file mode 100644 index 4d2663f8a3b..00000000000 --- a/docs/bidders/smartAdserver.md +++ /dev/null @@ -1,59 +0,0 @@ -# Smart Adserver Bidder - -## Parameters -The `ext.smartadserver` object of impression bid requests supports the following parameters : -- "networkId" - Required. The network identifier you have been provided with. -- "siteId" - Optional. The site identifier from your campaign configuration. -- "pageId" - Optional. The page identifier from your campaign configuration. -- "formatId" - Optional. The format identifier from your campaign configuration. - -The network identifier is provided by your Account Manager. -**Note:** The site, page and format identifiers have to all be provided or all empty. - -## Examples - -Without site/page/format : -``` - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "smartadserver": { - "networkId": 73 - } - } - }] -``` - -With site/page/format : - -``` - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "smartadserver": { - "networkId": 73 - "siteId": 1, - "pageId": 2, - "formatId": 3 - } - } - }] -``` \ No newline at end of file diff --git a/docs/bidders/smartrtb.md b/docs/bidders/smartrtb.md deleted file mode 100644 index ffa88f663e8..00000000000 --- a/docs/bidders/smartrtb.md +++ /dev/null @@ -1,39 +0,0 @@ -# SmartRTB Bidder - -[SmartRTB](https://smrtb.com/) supports the following parameters to be present in the `ext` object of impression requests: - -- "pub_id" type string - Required. Publisher ID assigned to you. -- "zone_id" type string - Optional. Enables mapping for further settings and reporting in the Marketplace UI. -- "force_bid" type bool - Optional. If zone ID is mapped, this may be set to always return fake sample bids (banner, video) - -Please contact us to create a new Smart RTB Marketplace account, and for any assistance in configuration. -You may email info@smrtb.com for inquiries. - -## Test Request - -This sample request is our global test placement and should always return a branded banner bid. - -``` - { - "id": "abc", - "site": { - "page": "prebid.org" - }, - "imp": [{ - "id": "test", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "smartrtb": { - "pub_id": "test", - "zone_id": "N4zTDq3PPEHBIODv7cXK", - "force_bid": true - } - } - }] - } -``` diff --git a/docs/bidders/sovrn.md b/docs/bidders/sovrn.md deleted file mode 100644 index bc6d42333e8..00000000000 --- a/docs/bidders/sovrn.md +++ /dev/null @@ -1,3 +0,0 @@ -Sovrn supports 2 parameters to be present in the `ext` object of impressions sent to it: -- tagid: a string containing the sovrn-specific id(s) for the publisher's ad tag(s) they would like to bid with. This is a required field -- bidfloor: The minimum acceptable bid, in CPM, using US Dollars. This is an optional field. \ No newline at end of file diff --git a/docs/bidders/tappx.md b/docs/bidders/tappx.md deleted file mode 100644 index f92e1cd4fe8..00000000000 --- a/docs/bidders/tappx.md +++ /dev/null @@ -1,13 +0,0 @@ -# Tappx Bidder - -## Parameters -Please contact [Tappx](../../static/bidder-info/tappx.yaml) to get set up. Our operations team will provide the 3 required parameters: -- Tappx Key -- Tappx Publisher Endpoint -- Tappx Host URL - -**Note:** The Tappx prebid bidder only supports in app traffic at the moment - -As for test parameters, use the official test parameter specified in the oRTB standard (https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/OpenRTB%20v3.0%20FINAL.md#object_request) - -For any additional information contact tappx@tappx.com diff --git a/docs/developers/Prebid Server Event Notifications - Tech Spec.pdf b/docs/developers/Prebid Server Event Notifications - Tech Spec.pdf deleted file mode 100644 index c0bcca753fdf8810619f620f2e24dff43fe346e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89983 zcmd41WpHIXkS1s-Gc()QY?qmt*=1&CyUfhY%*@QpP-SLjW@cvg`1R|V>5ZP){kOX} z6w;LZI~ofc8`>Cs zOa0r*>Dya50RC};kd392m9+za1wbcfZ){{{=xAdPU|{?<4M3;*?Foi&dl~-mgM_t_ zvFrB$D}b5p-@b_rfRW*Q8wvnA1sfYj0QK8X6es zZU5FY(9>gwB1+Tjv4lVzX+@P<^b^k}05D!)h8m@V5`BR7)Gq)O>^W1b;7PEE5qwwh zV5K6z5G;J94fSxtNq`^-VG{p|>wl{K|0u%$&1e7r8-V^N;{TIP!p07c4ghB6e?cIp z|J(Sx$MkRSUw{hfJL+57{QfVj;rK7}|Bq-@1h9Ss=|3^e&i;Q5ww~TUkt?jH^Swx%%AQ~JIATdm}x`Zj>) zwlsj^2_8K7K{S#k9S#;2h~*TC>%Ry7oBsdXN&#zYn{P8U0gV3;PyxXBFX$Qnu|xsD z^pEIw^Diry82|14>jb8MJ;TKE@8Ca)=v(q%ElmGvVft4K^S@e{|JB0$ua<8H3fWjY z8e4y_{+);ZnM^1B%~yRvn}1mO&lJ{g+Olyl{zJ}het%2;FaIkTJJ>kc8-7RRAIgik zI*KVee&>Y$=!r1_82?@3AIgh- z9CjZVc?~g?aPmz^vO{v?^)vZX$0Rgnic#U0^3R3pc;TUwB{l({R={7M_qPK+y1D`A z6}I=Av9YbM`45?@@wCsi6Ps5h$D5@s@5^AH_nWmb1ip&Pmaj)9SADLYs3^tv*IW^( zvykMj*&5V8db7A<-CFc+wIAm_x4+2=O5EREUv{)zB$+J!#^~zy*W6I%gI-0dgFI-0%wfH* zXq$E!{3S6o{-rZN9vZVxYJ|#mpWuX&(K;My_xt!slSHxb;<=%C>k4UsB5uAU-e!-D z@N2Pk=Qr_h`>jsyKG2b>2VU)cB#k3}u!}mV`y4u8@{3Ptf=1Vd#&Dn7>XpZpr`wVHGlj(f>$f-AKb8K0Cf1RM z&=^A6v(L^Q*RpNOJ=yeG*M+wSw;z) zfDGa!NjJaFLXBI3#+&E8o`HMghFiP4X&2L#*m--y{e#E5_~)(I$u#QsSXUx$9 z1$dz_N|?6}T}|P1koi(c_;!jrZ`I|B84W_n*J=YpfZ&wJIiAVhE6uW0k+y6 z=R~T2M26=@F3pM5yDEH5JJKJI?i)QMnL^Pwj-vU?dUt;;-Yl5{^_$`cbfW}R2Zr|G z+m$I-h|j!v^=;)u2-$L2kdfJxY>Tx)GL!g|B(6Ak^KkGysv|;h-Q_5HtdeUxyG6@eS@=;W>?LrWfP^oSLLW27SvR*G5MP}Q2`YC6cbQEP}!vE>_@P3h-vMEMW9~x zdt$6ckSeo@@D}}TT$RdN?^>@B$b%HFGG>8ZOkjf-K~k2Y4po!!N8kxcstvvB|%3^l78=Yp=4s)R(s3=vv9tv*BL2gzlb#GvDEy&KzE zGM$I>-fFHyP&@~xhd)B%(|Jg+DCuI5^E>I{G-&b?_Bi-*?n}k!N3ErQp!%ELM*02W zGjaG|;83Ucx_MAQ2+n!0$0;Re4N7?evWk~rHJmNTYEJ=JK_eWdQIC)ZkTnv6B!~bP z{y+IF+bHz&!1_m)4G%S};$=%C9k=1mQ0LCkw7W4T!Tz4HRdi8Xc}hy8NAmVWpd0Do zf$EC6FsoR#KSMH&V_<=_i#Y3}P2$AnO{J&}B0qB)R=M1^#pj+koB^6kkF6x(@d><}1T?HJ-Z4>z@nr6*-pXTLTGSG3WR$FvY0lYIk74vUx zxKqOIS&%W!$NGG|_Fy?FPE!^K5gtzS*sx}Jw<^jV+*Z<}q;3^@a?`!T9m^hCa(dM2 zByQZH#UK@R6QrjZR^Z$Sg_kI}$*v}4*8GEt)m3`wb|J<(?w-=RQAJlQObwTh%>$2q z(Eg@!GtKozg=M%MX{J02RVH_-`;xaZiPyoRB&C{21TWk=Jh%eC>w#S7^E{~k`uTeBjK|UqVC_}= zQUJ}sDQL4m_;-M*!>yJ|V-swxj&NBM5eV9nKUe+vu|m_a**z960JymWP2D05=EH(J zRR?7Vo}Ig={x6|>%})hf{;gWE4^i{iuKv2nYxJPe2Hk75shT2(t)tjKt}BnCtRm z6;M);K~mDDv0OJEF7A(Wm2o7u6^^{UHH2?;@TaY}rZ29s=>T&rrM=**mvd4hW-f_( zO@em=ed%j}+Gr}C5AzNx=R(PR%m%+T`JglnDmK8eij04Yt36ECL2ho4wKGeGmDd?u zn+fFInAbuQ^@P@()?&bAeE#FmfCAeJ@X&&zZGiO5HnZ9$v3*$I?tzjy54em=Pj>zn zm|y)6Bav;?Ts_bhReu`{)z}*Z&O32$WF*Hf{cW-R`FO+gaS=7sUu`$~$#c$6M;{SoScQ)XSrY#4Ze2?sB?uf0(<{9fN_)~= zQtlYA^s63K<;*2lgXxjLrDEVYBdpT$b0Oy!vcIEIv&_mIrGFSJvbuL<&@8R=K1QZ7 z=zkbdiH+h(RF&ByDx!hl|Ef~vuJG@iDu>%~@RdtqpXhOOj#7kEoG4YPX|u0;Ne3;4 zxLunG!ZW>$`v7*fBpJ+7&^NujQ4SDcy{rU0MdtuL#A6Qtik~n`(t!pV69m@EIXg}W zW9%nOnFV|CXP^(JBWi!6D7t1V+{rvbyH_&>)uc4>I!u46V0d1@c0>UElD_WPc)b0P z^{HtsDkmU>A!rP7p)5b{=>zd-9ADy7K-vn_!h9@lF&4+j9Y4kP-;UegXPN(f>iWNV?`Hrov$B7m4FBgLk&%Vt``+fiUJ9IdxOgflKhd>6 z^1ex3rNv8;DKe%(QN~I_3o|DAGM~f({H8M?2~kB70Sj_gkf92~MUth#zW&1n{zR;D zz`$szFueuC1te*1OT0`1+h$!JTOMC+1SYpU7E29J>K;vAlM*05fac|(KnfA?*j#&u zwY5`#)H;DeHMku|NuKJVa-@Jwk>CKo2pIJDTg$?5d?3WJ!2l{Tp4&S}djwtY0RF2S zU`~=4Y+l#pnfJ3_H<52h90j%A%#*E9b4!R&1%uM=$H#xdfq+Ip>1*8d-luzv^u|xNZ5^UDvN$hKE6Xl~ZFDWK!8Zdu27px`4&|+RK5T*H4D{ z_!ysh!v$Xbx6taKw{x7f$jBW2yk+BEZ7eU+&rS=@fHg>3$`*pVCbmFtLRi7s8fwyy zO%o}2-x0I!kQ$(HqxQ-n`uPLT=E4EZZMTBRsTaabbcLC}a=;p)=?OR{4)rM2(V-@| zP3KtqpWq7O{#Zs&4Ygv^?_`la1 z`F=uUL}I)JW81bH@BXC+xgjcEsISlR!#Qt|YKBTSeEC^P5N=FI zL^JrVA%)QGeG=EG&vOuGl(@~oMR>T2xe?ba<~YbI zFTMsijC0ZFbz^IUKa1<7#`VFc6Kjl^_PF78sO?02Bh~XpFTP+$z*6Ap4KNjK;pb)P z^DZ=V{rKozigLvey|L(YlV<-K&iusM;c)ImV?V@!q?Fm;~jogJ+BmWeHvqY;zsusXs zlun@OJHrfqpXApHVSZ&|PI}P;A64bz{*vmCaGm@W&kY-}$?nZmsD7SLBIJMsV^>KP z<+el+CWfvxIrR1v3M@RD^s3l!FkRGw3$ClywhxXt)NmYyt$4yhgI!sL?M8NuD(Cc%i)8H`c%0DCsRI^6cmj=zGAiQ^ZNlW+!;qMkYS zIOyPzwaYjsPWpmBc98v^rAwYtmVxRN^YH`k8J7f4c*VR>>&kXgiy(Q1m!9B6GI38` z%jD%9oAL-Ig(Zx<6a`7ccC4xR7pK1x(6y+MmkP1f{(v+m^x_`Jn32$QaiyQf3Q4P~ zD{^pc#k7pLzli~7^3H?an)xfry%KI9wbqz*s;(V`_9P*nz+foE4JR8hpKLeG@SMG~ z|6l`(Fe+P!H=-Xd65HHR;XU6BmjHb58T&zN)1^<-yAQ;DVJnu^;o)@5ZkQZo%@Jia z%59D~XYYo1ohL*-a$Kc2;TW2sL{vKhrqS^l;+{H?Mkw&GF@&8Lwds*OMs0h{GYc*v zdJoFE@bNDNsHFQJ<4Kx&^xDC!a6|dz`sfgRe6vt3!!uTlE+y0~v!ZSt3HBKTag-Pe zSf^VdNLr!WoVzZG1u5dLky_R{q+h~X_BcaUBrOi>lg!Os37fNB;nsp2htAKK_c6y#ob&>Hl# z`m?K2&L?WFBlpGLB<~bQn|omE;Fs{Ma+$YSknn`PciV^R8TVf#)xJSPJTBzbZ{H6z ziDE_rl;5Vi%`5MU{xa@0LzGIIIq2&5GyzhuPY0bxF!>Rx6wI5Xs6+M`;8dRjNO%y? z^gs;0Y1&=tIqF-|VZdsHhFJ|miS1!Ez=0{P-M|im0CrW5huL(Y9uygdd6A1P7~c%B zdVX1y^a9Zfzc%dXhbiM5B+}|+r@PK%gt<@L4a?DMT84PdgRe?b?bT2xnCF18>~?66 z{CK@?)8qTQUy~E`Cf{{fV3i)j3PJL6MNp2yy7#~x=!{3GL=?j+)TkE5jZ3BN!G+#j zch~v}nbAFEEc{5ls*f+r4WmpA7H(vykCaSl*(JTA#9A9^qMYWFb2Vf>Wi9=@``GIpk!m*(}&^( z9jU`n@4KOcNe7y@e`;fD-rnv_XQj-^(s$v$Hh`S-5#SpIkCxelech_^U*(sw=G zUYlQbwQMFE?4rL6yl0(m?+o}hgLx2Ie?w*oJohp$2hjRYgtFYDHlqFM?K1+!PF0@H z))KA^*P~jenz;zYKi|E^y2Jd-5;7Oc;d27w z&puF(fi#A6#kRNs&AZt3vkT3DdOdp!E(v#EqttJ2@^SbG7nd$%Bj8;)mzwq^`-}%5*s(-0&4goxSKDXL&>oK785S zCwqXqOw$wCPTj)|dM&hEdVS8}m&EP5gWG_ep_q54D)jA{;*Z@vtA`U1E3I}(*ilEr z=vfzXJ0IRq#bmj?hda{DzA(zC%Mx@>9o>P96p7o4Lj#Xyr}ib5W2Q2OYy}*&2oNIR z6Eh7X-=eDjDj&f@&L_Rsb`SI>MHE0x^jCG2Dn&+zwg^H<3wAODT?RZddqj71>lkpY z@II2h39M6f(tJ|rB$yf1F{V@oRhC#*T4q*iJ*HY#TeewtSQc1joP(}Ft>jE{O!95G zw+vi34*mVOG`VziN&Xx3w|irBW0Gf+r>|r4que7x>|WTdGTSv~Yi@jQjcNL>^0yFo zRLa2Fp7c$!t4BK(FC{PO=jLfPQ}B;6W(LT71!n4>{C`(H^FtwP0CBitJw394l_Iu6 z;O#GAfUWJVS?#Usu91nQhTLoYEGabyRqCD2SycKu^-}uH?Q)*uTv>WKVyFRBZz-97 zPb(d(?JU;ituxl5GJlCq8vnT?&cq4G^nl`MIbofAxLv;d-}6(| zKJDI)Ug+H|ML=7bP+JRpd=NM1OHVmDUY}DnY?(RXZA(a|s#B_2KKExMM}wIj&z=`8 zWk(n}Yqr)aE5^T+w{I;Jo%97wq za3uSbI>)-hvVTpP7eux`SkZpdUtz~e1U$ke?fXtdC_<=zt`m()(C6mLJPLvAmwP` zE6LpoveW!5*RvXj>D%V>41AlOeLqVd2*qF^`V;eDWGo)Ct}zBqBpP^g#aqb*afUUr z@sx9DCnn2hVb7FxO`uu+X(gSqRu9ttqd`BK9x=Jcg&qUF55yIiwbvaMsXVr-VcV<~ z+9yQh8h1QI$QHc1JE9iKJ3w~_yE1_08owP>Qy&sGsE8gmy4ThfBf2}}n%5I(Xxjr5 zn4r(@8B!-8?iz+0DyN71nsW3M;TnY-*ier-GRTgi1ECii6XAd!m`o5%Dp-PIPH_jB zJ8<@co*N`4KeG$oLLZzPJc%B~J1|F|PF*ycI1w@s3F$O9o0^vtwIbl$ zOJM7-6bEL6u_FB#yCeZOgN(QG%RDR_Gc+!Bo7+=5KQ!%}_$@3*ZA9=E+${kY05PJu zk60IHx(vQR61+UJwoe~M3Xd$7xo@+N_l#oQ&2~*=hs1Uh>v~J;7K2MDHuAO)+L%Uw zOeX>0-6bCy5!*Msg?dT!3`P(3foEXT(06Wgj`v9G5rd~Mw(@ok>Ky4c9NiGJJamroNck3-rANMUbX?SFRI+U5 z9PBkPS?_Sp{g&?4*J+HgEb@r=maT2TS4*%Wd;Z%w?W4E4w&MKiT=fy>@1QsNRy@o( z=NrUZz{iMnE$1>kmnqsv@J21f=HSMR%raQWj-4yaVIIvX**)qX7@k00LEXF@IqG|4 z_r9xr5BFFe@Hc%hEzt>2tZbs7+Q3FK#o>fwx@FvQ5i7{|!?KdRFK*4yhUpj#_3D!< zwPGvg&B0Y7vLEr&TEj@*D9`xv;M32!sdC)YT;uR6&{BybXp%`91q&x|qKb(t22CPE z%Z7M%GAU~)SJKxnC>bQAC55b3$;sJqX-OO0+hZs*qb&>J1s?huPcWAm7tgYMzu^5uCN3>1zi7W3X; zH;NVWy)H+Nro~L9^ZdSSf00$9QEN55i7%4neYj_uqV=$T2~V1;ao-tOuvo12F#8y* zY5$|YS3J?1TvcOLvDs;ORK4M$Q?sJ^*X9ey2A}mti#i_8B010mYW^_}?n zIYE|>%kK7$u9Ms8bzJrL)J7EE)nK%X;n3f@y^ZA?>vLB&=XM;f%_zLD-*tw$PG|i3 z6&}zU&BIe;R@qreX>r+$m1gFW_6iBYU35J-S9X6h?V{B@q#7s88N|{e!8>Fsx}G*YGH%;l=+R+2i*hShqeL!jY!okO6VlskY_PhMi7!pD zAX{M9EhS(tK`2bqPib%eRKzsh{jm`L!XHT0nV;0$t?r~Q`YU--q@WRD76ZZ68j&y7 zxNkv3jT2(sW3=K^q zQ@(m>rK*ItXEpR&j^;TW9HHWJv!Jz9yK+czmbodt5$_aF0dA9Z;T5$Esxc|JEV-#F z`Bz`7*j-chBM)74R!YVe6#Cj zRa2dyvi!X%+mwp(goA3$iM*#pRS3Z^$!px@l-Jc1<))!L3?dY+C;L59M;Qq_mb3t& zKQBqw70|j{wl+v~lsJ3UD(-8pV#-mJeMNPu%0g2iJY46G-J8>HwHR#`?ie0C3qf_8 z@jVx~YZGz{cRN`Oz%Jt#EYgZCIFb5;)=MYEP*IPI{nr^z3X=#$~>^DMvhJOR_aN4V01?x6(R(Kp~_taR&;kd zpD&N`J#olsQ`cPABq<3Q`N0oSC#Q*tr~5{v#xc9Ln{Yw~F8lzRqQdm#oAiJvmSo=c;`+KnlP5+x z$y-{prQc;L&o-aOaEqn+@-*dP4}3}p2um)P2N&xFK6QUf%c*XTu~ zt7uOV{yarbGjWPYT)U&O8QWszkxueOG4Wn)bNMR&xnSi)VZH;*B?>9YeNX$?x~3vT zFfT>$IUfjrXqVWp0ugI$Qj#TT5x z8BLIEW5e(mY!)cRVP*Q}x5g^=<+CREmR1APAH+X`pj=7r>5rj+F)ER$<)U)^sTGhB zrMOVu(<%@kVd-%ZA%lymrv?1Zr?HtdD@;RmfKghH>F(`YgLO8`m`tjzH^RI@*AO8P zK|a8{bmwaH!Fgh^bB7EG4On)8cG-8K1x2U!o^ZXVW*e4IslMYT4Pi5*Vb!?4AO@?7=b4C@~(MpfbgKRGEu8ytbD#k0N174U(N(5#;7&@m`~e z%DkNOXQmNlp{|V+8M`X8EE&E4^B)5}ynP8i;MT$}!dM0%g2818shm(rp+%uAq2__G zZ<+nP{ScuEvC)u#Kg6i_1$gXIa6FxTTZ0R$l6-|%{%~pTk)7l-2*+H=UOAuA z`C_r0ari}7=eB=xo3PJ--!NmbV~h1W#J5({^z4H)a87`YVqjv-LIs7{`T_mOfsy#R zH(bGOQm*k{wo1Uu_7LxY8CAV35D-HxU?0sy1~$HD&Gi6E?n>vH$2ni073lHX0G}Jh z)Ln?L^Pqp~#ij<;;zv;xP5?{P-Wv8_5$0!U>}Vh4xfsm4GB~g%jc=sKQxkdr+47h62N6;c$}r29_nN z!l%a3`8`~oVR?k`CWwVkAp96&K?PdhsP&tjK?WI_Jr^>J;JXP8qi7p1Ixifq&Zx0# z)=VLKt=ty*$j!lo_9*h{y*fk&NIOJ3FeJg@S|@H9&oIaNJq9@WP|UzozgGWPiMcla zTzX!{0jt^mS)zo?L5F**_rbj-As#>WfWBCRR#mlKfIIogAuNX(t=bFimXQ*sT}$&3 z!7ATp9+z4FbKFX~m9G=KjO^{#**EkS(o;A>M+p|mQ5ZoN=R(V+S_mG2Qoe)Ns+(*Fb}{k$ zN|=Ejh#!1UDr0iI+$iRx zD{_e9o^Rk}D{^RRfBP6%gHqS>FIsT)h7-6_6%uSWm7zvZegYvJ|9d9kK|6SX>#MVEf1+=9X%esYeeKO>u zP8)=LS*Gkq&irJFlH>*E&PB5-32M_?b}XQ&t}Rc{Tv6X)YxS@cVeQ#d0mp(AH)$2O zN#&Mxp{kG!9)i`5vyiMG9=&yK6VRPF*K_)9s47Fdw8f}Yn@iNG|dbdn= z;PZ;LNk$rUZmG`b2($%}=oS%7JX($+g@vZA&C!3c$=gU$FL0d#hs7B_*V9+=tG#bc z8R4PX!N!JTTUQBTdx56oqPfpEe{*0B;zw}H+6?1#{V-Rn_Q)jsMuUS5&RJN7p|Q&s z2Km{fZ1Tj=Y3v(axr3V%1g{D$0aZ%O0^zg(HVg^Yl}@`hu$3hJdE&85xk_Gp@_@VK zsu~=MhD7h$EXs$PCCwh)V;KYV(sr~=#I~}uCY#kng%Q83g0JFHPyhkW@oenAWl{Cr&BHMP`gX# zHr!uh&!oyNrqqfR5%lY@7_5E0=`DbR&D4?vtC>h-4bL1 zB4KhD=j$2yl*f2s=x3uD6v9mMB?ZLyypSPf?P#eepONFZTf25JAq zUa`9i@S0W%YeEdDji?cXy%S>QKlbP+l?f;iN^#{Wxfiq4LILguUzQZ@Q(*@R8CKqQ zd3@}p-A0n-QUjaY!AcF$spL!M2Ije;GI(JkA!~%0)uVZ>YqEu1dkh%LMXw~`yayfk z<>)P0wtzP!#McmV!9<70^tSQ#T4kG_9lljO{kl_zaHGxv(zs$E6z5XLN;@AFQQLGVcQS; z4j(Wxe0AXZ*g(PJQ5uYop<|Gto<&XT%3IincGGuv4bm>s=vrj0Ab?L!KGM=igD0F* zvIRL+8+DHIqSt=vM-PwQWAY5a5XU_mur4bacVdL^7GDV%8W!6}wvzUktGf@Um!!bh z)U&!?i%_mr-_^47J6EV>cV&OJyC%pRpVm6|yQN3@{YBj#s@jYZ-Iwyr1F?k|R~iQ2 z^P9epT8jcvGVq4I!mU@~{Sv*?#MwDBD0V7sm(i}NRbZVOe|KtbKc?WMEt zA{dh_GcBttMIT*Y4Kzmnj(H6KI{3Bkv7bXZ`mC+A3|F)DS{jgvVFE4EYpo{Lpay%X z#w+ZG|D!Lk8kIE38gO?~V1yx!ND$vtNHgMPi`McuHSKb2?w-|*`X_#}5F>J@t8A2y zk!i2%MZ0@5c+8SdXD!6_FvJM^7PEtBt{a}{W0UzMBX&|D@M$N}HhOqPgMC7&UzB!E zHRzH8%%)bwfWb9T`8r)r20v({5t3iIL@m_blVBy#Igf3G#0Qcv()~(^aulsINS!{} z8#qNi=BUrX2onb?f-piVBLiBtb#H z=p6b3nXJ#Q*uS9{J@dovHGMis8_gqLz7#m7SBnB+A~0mY;1LQ73giTa*BQTR&5f)U7S6@UIxqY<^ARj>^H0fD8+SxzvN0cbuRfx*D&@$GjhruwSgh>@~%+sdE* z#OqYJftA}{jvl2Q^uRSwxc879*!Do0 ziar91D0R3uSibFeK%*2Cbrt1`n5QA1aJ+9Y>CXD54!zSfpwl%V^z$H?OFPNrXm#L# zQ%+wuLSND=^l^w<{jK9q{r+p+rasi7#K>zUvE+>w+oE zd{A+7(AY@B;&CwXft!r{Bgt#m0#ih_&-Cj|dwt-$eBm6&dSbZP{kL+{1uIzyK}+OQp$l*2 zEN?GBR+C2sA`hsC0V&--2l2xUl4|D<`d|i4Gy*dNpP>vs1A_y5!m37dw4(48l)^c^ ziH*BtaF68WM%0tLLWf_3c>eWh?Hn63%`l%RGNK{GXvB~ILcN`FNkYxTLQY=QQ4&+) zp7mOa`yz!4moKE1NE@FLT~MaY%Zr5tKnl>6XSxeUaRbIMUyFR_Q`G(PMe)a+JSIcE%8?SU3;RNW< zh5-$;hBOiJYQi?I(4Q#3Tu8kn8>2PiL!?u#Om%i@pzvTzrCv@xeYw@)$r-Aup^p}yPV`!GsYYr5m#F_2kqT-6vsKHRGt!!uNnPMxomN z-k+MDa&2V>BXjJO-rZ?+2KjeXtypM|{p@a!Ick(D6sx&(>0cQZUZfXPa?}wXBa%7d z8A(FFIZQl`X{jh1;2z~FNcHeQd&!O{sj6(1`e#NS!|EIvxebbxo7L+a3l3PQvJNWk z=YB3wLwId{$Pn?E@Sy&(xVTC^InLCe!n4B376~bd>r@A|H+`%8q+MNDB}*<|yVIU0 z$k;BPZY*msV@iTHs9I!?STlF}YoRPX@l#pRC*IG9oI?}xbh*uL)6ZBQC4W&E%1qvm z0^5nqT7o&OI`0ECI#p#d?2;WhB_)N~HY(Y4$q3eQEX-5N`o+GwsVJo;$)57B?S(xh zvXriGFH8yyox{uwk2yQ_659BjodHEGGS*4qeGVUD9&g$>PEP1pX2KW-7dh3OIYR^q z`4U;e9UA%Ut@~b46Pd8aUFA6Lj@k(MTxT|q^O}Xw8m^E2ir==Z@Fuf~uKUzZ5<(0N zCpiY(>9k$#(yu?@Puwg9H+wwI)_9zbFZXD7vQO!)trO=h7AD^BQnns9g=7#VQyo4M z9{MsfrM;5@;LQfT)d>n|JJTMbrETQEXJV1|o+?VAT zda-oLUcODm{lb5T(VPSIwqU0|Z-^3QIpPOh^Z2y3W53c6i}OWyo7UZGP*W*q#1Yup4+ z@*;EC{Q2g@7EHxJA9rR4P$xh0K>`h=tQ}4*i&|H36&#EsUCmOpS(2?k?vbm9L~%1OGSMe4 z)|$qGud&BbGKvy;u@{39QDH`ijP_>r>>{7<;F!L$j(GE~(&V{A&u}6Bx{RzdfQv~V zpTeK2bYXEKs02dgiEXh2jvUUsoW8sgbIMbyip}Mh(k9C=SQ&bV!PUZso?vmQ@T%}I zp8}69r`9iroG7iJJ}`f9uh_pZ_Qf45-WQ%1zBX=JyD-k2&zoDJjrtB{WL1;oGE65+OVE1|*jEa%IQj+8&ja+Xqo}Sb@R-(uB^f8AP7ICx%X39c=uO0kSCng~EC%S8%>Ynf5Y_$#J&X(aOWsW~+|g=Bt#CS7HuB{iI*)l^ zk7d;8_K>1Xz0q3rOxA<%NqUv!QM}@$dWjPkum4EZ_l#!fg=6Q9U*=a`k!6l6f3xgo z^=_!pro3YQNfk~7R&j<&I{HJPS=UMahrzk>-$UQm2MTNpTg+WpnSk?&Y30~G5USXp zLaLz7C8N2qBhj4aCLw**w#)K|sLmm|PT2IgD=QP?kt`(cEsJ4!Aq~NoFvm~}r9Ulm zc0Hfn!35)bo?j0j8RqCHqdC2g5wxJ&taf>SZo{Iod_JAP8qicXqF002OAVyU zrzfRHr4wVRhPeKHxN;+Z)3^^o{0M8#qf+&tI%=k&%O?0Pwa z*{P!8(!@lI;o>XUcuT#0$BypQskX!%z8ZCCAu@?NB+3YI)o>m@@XJyqtt-O@m}?iywtfmE$*pt12VPALPTf)HG{c;F*tF2yI14v=jBe0UVvSk`79M@7F&`L zGc-iC2M-o5Kx&jQ+3m-4)9gMAdqQnhs`h_&b@3b83-p_WPzJR!M1}?3D5W)Q_w?p= z7ibCf*X_nr4A@VtE;!D(oukFHdWn+*Gekj#=hmFjB+axdDJrm^q`w8T(_lh=JgCai zsAF`u8Ry&l%H;o2AK-A-aEOeQp^rLQ%{Lw$6Viy(>2M5sSS};brPO3Q>69@>_>r@R z3(vA$UG~FR8=i9)7FL?65J&W;eC6!Vl>7mT)Y&`Y6e4Kv>$A9sdYiKl;O}NQr%JGfxgJ7 z=>#C}7L8}`m|QIZ*Z%^{&m|60J0PMHN3Vdw9-1?YHd$^0Is zI#Bv-{ME-U*>HE_c zaulW?PJN|Sb-B>Cx>DeCLtBi2daKdskN6spk%7!u*FGO{IZNdlND;Fwn>YfN{=h)< zY3|T>XVFFCqdUX98ArOfX)5~&u{QH*w6a+ifu6fgD-`w${9+*-lMc%Sx`AbJb#aEb zQzbX4GAg=89)XgGcB;Nh-9$Z8j+&2>khREO#5lSyB_ksVB9@8Lwr|fFl^gmv=Kl7M z?O~odN8^?>o0tqrq95;EyYyGbnmG?Lk)-o%RF>&*4-6%NW>>sE68}D0%lBDctfM~x zi#6Aqu3W@KZJxd(@|CS!m%(5^ll0@~mIkJ0`Z^O+jpET`Ocy4%^dQ`Q%f(jyQv*zZ z=D=!vM;}CId{S$d{r+P;Y+lI>XTHVIjf9iMetLV|g$HkDkJnu2^wTctdw4#!+Fx(gkHoErPnwzXzr&dZ2cSmk$p(!=))oZT zme!|c*-R4ISuc_1K;|U%gRF}TLDgm1FlCSPyJ4LJR`h;z^RLd3Bnn2?=aG`2JOBny6SrhMjFRQQ zOhuLhdm%$aLvHaU!`H3V4!MNt^t!RXzB_7(YYv%=oll0Shv(_ISp(kd%y^q+OEl3c z^N8Sn9ub-S{r2*dQSD}~eLb4+=Fg{eAy#ombgJi*h{SXI+qwJ7Ij%YUx)%pdhKIs7 zUoyY(?=Ebdlgnq*<$B_})_tVoI>ixWoqdoL;m=h65rh1T8Kq(sGbN2AK!e)EB1Hut z0?iWNV0MR4W9J&NV)WP#wG%>Sa7u~@3hL5# zt(eEnQpT2R#OL-PGQWC?$98LR8^WN9w0bOd`=+l3JOBscxAnvb#p_Pe)o0xKzCta$ zlV6v1W$+s)dfKrP#lAfvW|v&jY#De^W=&e3-e~d@I%k`*HI4F0lN45)x%AjuAIv}> zu~}KQws@Q3HiI{-J9ReXVqM@XMo{7_O0Q*|vqi1#FS)--Q$M13G6I<71&{danG;A` zn=CV7WsH(=40R+gPBRKBG~ine9oKC{!D%}j_ThR}A27_Egj{&dBdXwtuVMG(ARhDK zpSL6Uy__(f0|prD|(8CFUWY#oUjIGp^4C>4=+W$#J1o#Bt-)q-!j`$vW2 zF^^m-LVphO-G7RfU6@md$RCF*@=MDm7^N@?joNRBU2j8)6yV(ra-K(Hh5+WC;dmpq z4pg60x6UFK2i>~MV{Y{f9TGL8+gxXk2;o^qLsvo}N@n-`egGrP{yqL5jJ;E=Fg(;? zeQevdZQHhO+qP}nHlJhLwr!vDzu%usGLyNPG`(rBnw_R;yZ2fY)Qi5!qwwii%Soqv zN48o)+-GJeD`v8YAp`XmK^+zM)a8PBa%vu)+RH|7syoIuNgbGzn4P;{8Zp)NW}URFBRMmNiwmA zittrFDWutua-~+Wm&B;5kttQE6)<+4I%-;#q*_&Q^c3i<&~2ifPS%myTe{k1xdo=Q z(nZQrQ;h3LDb>$jfhcx`At&i&l2~F&3=B+O2zoqq{37-^T+0N=*4TQ5KlUx<+}K@* z6Y%@XW`{K(9%k(63oLiFeXk0U<$p1GRCMjQD$|gys-t#F8?)X$?0<06;$ZAX3EWQm!}pe zR#eoFO&neJO-&1Oc#1Bot#g&B3n;@(pG>3R`zjr?BClLJ-4Zj!5~!H=B~ef^)U3SE zJd%LGkf1+Px>mL7B}{?B64Np%WY(2M*aA{aZjtt)FcT0JtkOx``Dq1YSK6Cs#CR-g z3)nDxDPF*Kzo5YV(XzWMyGEHB6wt;nyLHQBB*YXM%?u7l@lX`$7*!hr4hiq3Q;)Y};pM_4XU{~3OxabG`EmtZ%tjH7o zt)q#xQm+yZKY6=8I%ajLTf3;z#0=h25MmSx`qUWq#!V-uNN~~=k4Q&ONrJa>ZDR%x z^20uZJ=+1J#?sWqAd4D$!P;STd5GEbGihoI^yJM3yQat=Uwj zhg;||!y3&D!4Q9d)2pi1FGXkqB ztEyo?%+>;R7KB)zpjq(jajZ+TVY8=Suy5I!T1O%SAlfeL&KciO-CJ_^V|cZ(7IM?I zhFYGKU{>tB;_D?MhfEi7OfMcW8GRSFmW#@|_#Rcq>HCTMK9A$S=+fOL({Ox*tCuoE zZ!ufwHWuqd38Bnj(@p!M$Wg-4Bl^R0?_ayaC>~Y`{;m0=B4=6^n*%!A@7cO$^)#9` zR?-jcD9|>v-@3zeZE?R!jq&a4{CYkj8Dw-QXi;;6Qii-XXmbA`uS7d6x-o3sBPbJS z+E6qr-&LNO{vi3EG?RZeR(<@(&g0^U)DzpA`|FF=*T)w37WnJxtMj|{zbza?Ol$rS z|BS|r{w~g$e=(q?ZsOK`_P-Xrjh-qEY>CFR--~NIEQkMX(kbNuG7elUL#rh2r%>Pg zR>&>i%GJq~P2llEWpj~TtVY0YV8X4JM9?1I+I$#HGvP4eK#DssBvOlgh~W1pXH#^K z2tGi7OFPGGpbT*`U=dI=ujP;X<+C&XjiBp&Zgmg*Y*k4vB)&<0&A_wyv>R@phWmRw zPVKXkyiQjDzQOw!vY6`q62IY-0L%GGhOP9W`g|mR3vr!WDUOy?Y{HgY(PS zU zjC|6&jeZQhtnVTe_V6hLLgv>c7bkb8dj%Nh7#BSf?hJi$R(*V)wP3%neoA-Uy@2J4 z9kE@`h!yWvtWn4|E*rO3nUEC=RwEXtN>$I`9kp!yL#CH>acR-3SLXQXQf*Rl9}EeZ z1R*5k<09JFO~d|$igKVdq7!+tNYVZm5%2`g|6#d!lXJg=^Kg1IHcsxtE>sF}{`3-t zCZ-Hsmt3O5n+oVn7S7-4~p{<=D|D|+EF<}+HPaTU=#ZYHA8V3r@lyhv72B{ zTg&x5&{CZ5X1iD}eZE=92mfk)dGXDv$!)&jJJ*XMgKpj3`*vTI?ARCbMFu`4IeH!H zY2`U&LP-5~09Rbn?GG%p*PPGbB^_>Hu%ci{QH?;xcq(l}cg(#Bg?plV7^QQAv`PMo zBy|jY_{r)?B()}TC0_t3R75YHe`9+_Ep>Hwdv$ZKj zmu0{8b}#M_Vg3DzU5B|x7v3JPpcN;Dl6qC5nVGmF5cF&!({*JQdmTYzmYup$va%m_ zihtkbHt!uDx3^sV6_7Rn?iGXgv0gvd^L(b$sMhal{IdDY^JwaL($eWl--G>a3;MBr zzhC5MOSOp_`@9KOe%#06^Q}E3=FgzqZtyVu)op4TYrE+5mNGF$P4*M8mJp}bsn+GZ zKwE%XiM#>4h_9lwIv8j>=N)N5Bz~*=Q}$Ou525NPhkW#Oix5(ir9$q*7MXkj)#@5lu7 z;P@VIx`DiMqBeZ~i&IyYpWB=L;IE)N@=4p%eMFt}!Lz&{NOlOmq)MwxbIC`tOOxEalp!q&t%?uKJJn~i(kQ^o z->%)VL=z6|VvK6MT5P+l=`zZWiEOJZEY@_@iE5R(CajUGGWT`Fk+W;)k@T+Qvbzn^ z5z-y^GL_$Sx73h7PZmL229XO_Em}s57BOX&6s-DKtCb)!sZuf~;8>-qqN42r5lVF^ zm1L(sKt6@8Vd!g~zYUJVmh+fT$0R zA01ra?i}xW`v(6`#^Z(FCu?Ihqqr2USpR%~r{v}ssv)$doJn|c0eR6SN$==9!gti{h zwIuKxhGML?z0k5hF)xC$OwqM5a(q6Fee8V76-rMJXbB>ZlR5^DNYyfNidoCcEs~KR z=`AKh9WtMXmeJFpKM=`Q%i)Rqh5^kkkFp9%VVdaY? z<5+eH?I7(~=}M5-t=cZ>E z4C-6oe}ew%5Pq8hvmrr+-QYIc`iDn(XUFrhCYhklYHb zS}d~O>^6OM?=YjPJJ8h;?Abq_&5#3YM^r43N6d^6Dbl#8c2`WH-i(=OAmqsbsHuf!Z&GU&p1h}RA3SI@j2G`NEmfMsY^+kSeygE;}JD%whs>f){ zER|<2jR^S)To>KZ#UaR#{LI>6uz*F!O;8MTDHy4uh0EdQ-X~^ZECX(js#RrrA=yC? zO5ZDbB^ao*hN&c1hjdC1UWkDp9bG42yRn@hfB3dr^XO5<+Xz9GV&5X2HDXQj>OHSw z5FG5z0_?tv03H?BOphZ=FFvp6BDceDsNq5UevbY&vUJZSE4TIVcut}!8U zR?2<@#U{S{akFxtpX~R3c$K-%mXY~$Zm-4fHDkmi?&jr)B(99ga1f6RwL7Szg2! z)tT|K=p*W;JR>>@L{qm8g&Vzl){QRs(TqiKYkD(?ZOyGrbo2EzF3zv2Z!>W-y6LW# zeVjEnPy-})vp{@8X(~3Lh_s@(8R9+0D}UfVF%csM&^WVkaXcnj1`_;u?Bwj2kn^bL ziVXdjarXKaap2MmQ&h@Jy>vea1=Q)AR7`;kI-|q~dZR>(Z{JybM0B?`5_U9*%g9tC)>Tv5989 zxjE2~&OyThzfgrn^;HobeKhJQb!Y_&7$YK(EUOR2KBIo?1hWi{5i@SJtnPI+4zCQ5 zkry{u_UJko7k&b-NvC|R7}kK)t9E8}XL3ky;BuaxwT4k4IBsVq~81aPFJW_v?T5p?A)Hl2<+g}fxEULMh? z9Bev?AT_Od1w91MMI6Vr6J+er*t@p@i!|>Nhb)ipBwZA+)w18FxA1*bBR=P#bBQ0) zl{ijebhex#UKUT^{EWWD=hohq=#F_b3yI|Yjs1MEx3*2idI}f$)DG>I zf!Tni8j{b9mW<~Nf$5NpGuG-B>{h7mw#YWFQv;aj1NF7x+sQa(3((lzLL#&*qd23W z|Gq?LhH;{x7_#F71@eg&eOq%eJvSDASW|~mdr`)`D7nn|D?#CP$f^D@oN(FImZDRA zDBn$$`Y0XZqj8~wZC$1wfW2)fnv`alG=HcDIA;)&*W7wZ(_ zJ+`tqxC6S@=;fN^vzAH+Z1l|J-NNOktI^TmW9#&+=48*7npU zb(7S|>LOFkJDQNBlCiC~yQ7r4yt&9_(c)WXW~roUWqMILB%Pz*rH&NKRyC_E+;V4i z6qz7}Hgh;COW3NH;()*hOs^9Mt`!XzuBVD}w@^`&PNSDTiH`dn7lUaFsGU}`G;Wjh zIt=zu9|Zhy{NePKnA7~a9b>XDt0xIdby~E_v^9vYf%r&*LymR zr`Gdy`3$RF!rN4^xjUG`S>Y(4n2;C$9vzuao-(X$puJ{l>AGVry%J zWy9bkK?5|3FwQm*HVy)A;%Ni*!jNop6hd0(kyvCYy|@9|Df4uETDOISf0AiFXp?Nq z9)sN=nIZdGOrUR(-CUfy{g1RMOuP4≧}THO>bd-n0iX-;CFoZX&% zBR48Qs9iX4&Jt;sk6@v*%tEVB4k~p{LRr&oskkJ#28zym=r!m%&w68|+p<(npDJ(y z?IE58l*1dXv7EWqwpnbiPBiCgO=s2JlxE>fFxwc>rO8>nV168Q$HnVb;Opcvb!kr%XnC)Xf3x-1TN(SD+H=&P?zaIt#wBbgh}jJ)>t}IZSnp*{qANU~8pW2scMQEI|&?f+HeUDjZhzW!cn7Mlbv++c5ci!>+LMzj|9 z7_g#-A7BM(Yjji>sh$=}+jBx~K-vh9R3jEt0vTwxYCS`o6}fhJ4kf0b^i94jTpB%< zg+9whr^_7mYlQpm`ykGDRW-f|n~wKEY7F4j?w|kZ-ytje<%~dIqV`xVI!5-1KX%`7 zM(cEk#8l290p(FBu@k8rA-&9?CR+kflCmDeHZ&>D6qZJvrUme=!_%<#u%nsEcf>~w zGvAP+sZQv^*jML>P<83l;?%2CJBD{C_Xgw;-vR4w{(8RSSqFX0U-%brK6!LeprJUc_)+!^uMU+yCy=aav^NAfaWy zuhGwDmnZrq`8`_>m+|~XaAjj&mBc_!x&O# zAGmB*$*iqds!3+q1t|tlwwWvIt%J;usSL=dgzIxf2A}!C)3RvxG3Of3LgaV)RbcM0 z`&2C0>r)`}fJ6;4nhu5N>gPeoBpgjY^dxzXitp=l=J^^#tL@o0uGig{kG6CLPytZx ziMkr6#d8Hw;?ggWLRv!wcb)bit+o;|g+p-+FL6L9&gfrd6UD(2(DpeoaiA1QySd$C zcdgvGsB+A_0ZFwVnQoJ807tMN$BWh2H%=sc$TD-Bd(V=g^EPT`v2O+C)sq^ zANR=YF*_{Hp|u}!XJdcbbG#1LRY-p{OpK@8U*&HK;qABDKD+m_J{9uC{Xn;!3fY0- zfQLBG&q|ON&8O>e(KG0AaiVSA_!u07ie9D6dCYm2@YD`+)yk#AAVUvz&=!nXwWnlF z)EE`-_m(dRgNB=<&Vs`69(WEo&pB@ld0}=k&KAkhJXePgXJx3+(=9UTWT=lXu92<} zo1UVgo1uy5u|&g?sjhQc1DYb6W*K^L0b|0?U3Q6hM`jGy>&nIWcb|`~By(zZo0vXm zv>VStQHgP(Z*!y;-^Plh@qL}4?xr;19vn#Thl6^~{XsrWiqBcw9jQNDb?EtrLp}Q> ztrE3$pn0iF+&Xt!2b%{P1T~0XCVcBy^C}3bU?dbdFR|`pls$pW8@4B(F}#MgXAs3$ zD~yI|x}Y_SaaCqD<=px>CVgdL#h@@ri-8JnzU3Q=1Uu2e`vJ746alIXM2i=`eQ@5b zz6#q{pM5vh*qceQ%)!^cL+ymy=k3qYH5welmtk@QHl00Fu%l`A{`RZ+%XQqpU$62@ z=JI)Zu6~EaZ?{}C+!RvdV@mmRy{_8*MNyU0*!Mi1a@F@daJzO>;hLQT=$S-3?ZJP$ zHM7WdsavIOs+|-HrBgajn~Fd^p`+^Pu@v2?eWJTd_r=~~L3_VzPhoFiL9;(-Pa9BN z?I}5Gd#=9MpB#U?N6vE2qUNZUqN-zR6RjT6nrIhcuVWXzF4D{P&`L>@cc3zXH3JT3~e?fpEw?v^g3_4_MLFOi!6 zYSni={$kho0sLxZ!ZVxy(LZ5)qwkg((WWsxvr#cI9h1S?v6Ax;Z$NsmKw#c^Nu&t#x>XTg2Q1#KytORdINSi-` z+mzgRCG|_h+tN#OT35VR`%-@OAKdS}T(6Xso&6eUKc=CLi{;CQSo~$lRmCy$CH9`C ziS=oX{oYr#`9H$n{9fTXvO3@gOVkwU9>ct+Bx;3fsrH~&mhsM>pzVwL73ebJFU*3if%^EY^6OZEjd{AuqPgX%l(Dz64>+jre|Ue@%Tvzs$&J)Ev4)6A*2wEm;$?G7E;=lU*NoOy4<-n8;NV?&*{ve42Nh|Gcc z1vdz88*Ccv8f;A4qp?M2%Vy7H(}&uJ`BFKWB=v=~s>$%esnwa4=HQJhXXr_h9$Z zd+fbBvDrtm-~J3wrFryUl=jjuS`!|LbNDCS%V9-w0V^VrDzfYfc@AG#5ouFK*S?ZP zRc$GtP!>f)J;m4<=2nvAPtw1E;pCCsL4K|djXqjcc=pWumt|GW>IZ163b6HC4qSk` zTm|7mpMxTaDxOGCwx{|f!PcFcBfZz+zVoJvkUM^Ig!gL{^V4lYS^B_wJb^H74|iUV zvbD3Jf>52IrVAEn8m5~}5Hjjiic$&)r8WjIO$Q4%BGa8|OhGAFW{ynHg)($hDIs)X zwE?6`ho&Vj2Qam7jKkSdYX!w;>PMDWw)MQtm;Dy^A*1|+zfEH z*=wJFy;^^DrP0;>s=ejX*S6!ckPmIKck8@>zV-E(>%O;g@BJopwK+%&>H67bjo#?h!E{Es09XJ zECrSVT;B_Y8Mj5C1nVRz7$Xm;GLdDgL`hLbUo$H&iTQ;#7$rLncMNrGjT2=U=P}^1 zEHP<4yPz{Tcka@MXh)*sc=pw@0BIz?cNp%wNou)t)ggfO>#EUb{t}B1TA^G$*YX zHSe#;_oSm4!K~^8(Ddj^=T%j^TSc>d3U^FGB?)v!W+j+JS?>J)+RD32mPsb_x;@Rr z>o9-xPNXi>g-U)dR1)uH?72C=vEvu~B+w|m`y!t@?}tk4xcLy4oR_-wN7QZ@y+I@B zu|B*DwVp@GW3sB@Og;ebWsON)&M6@MB!#TVt?pvtpA}j41Fwcpq3Wc7F zLyx3Z$m)pK7JGkahX`6Fz&#lnVq=w$UO@U~Gtk`@xh=dg!(-v^<0Ih-dxzRb@(ajE z(ia|$3Lo+psgK0Ze;wY9iUni)fk*O3VW0Er?d(c}|w zfeMPiz4zHGYGhX5fUCl^gl0aP3~0c{>E04PWZyh3To z{ueU(+NaOG%D#$z&-qs;|MyUW?W5_-{H85<*vwjgHcJ(DvaXejV2U##?)!&RKeGJP zoqUP0&FFHs&|?nc2Vi!W@|zeT?1%s{lMf}qMy@I=mwJ(X)4rA8$oyJ^KQxI`R>?3i zZ3oOxLuq%|$!uT%6+&<0A5j`JBpEfm^!NZrY^YCQB}5Q=eq&aBBU@qUDB3U9l~Y+P zGzRy(+Y*y@_K(!cdx8aH~e znP*Oa{@&CzBfm7SIKR2qs1FM6k({PKl;%VD;Mw(AoBkAfXMKoJCL#2@STI3U&x8#c zX*ce5^-l!g#AM`!YM|hPnW~==`(xAs-4rVraml1uwFTQ3javDL(a8;)cI-HJA*bHT zT4{vEBt>)2j9r_SNcT8X(}>MzcW0x9DfTWrIahxQ4a%-k_t2n*oWdNKNU3zm;o%Mu zZC=3|SO^_AYWtq;m^R5Om`qaOrpTip7|7qX;-&Ohda#Msy_?SU)l<8`|0aBz%N~=Z;xyDx@kMh)Q2c)s3lgga%E!oy=HpuCM=FW0 zfigRZiH{5B@EBMW%8aEQzlOvb=ldmdcudq}xUuD$FY0pTo*C?TyhhdA`2;O=J2Ja(y4aTkwKsf9Kg>F5K+RuTkyi{nGgJ8}Yw) zw%=H&d+|S@O?t(nFX)Q&*g#{wKd&wQ=KpwX&3p*n=d`Ae6 zG#`sXRsTV1y@rR^ru2=yV|&ZAYyPV}I@DWo{0#XhFI%g%m%?YfgTB-7qx(@xF^w~B zG4+!pfc|024q5xKWs6SHJx&*y726WIV13RKknX!?`m;uxJe<6vs& z?^J04nh1NhlrR!>ehR`+L`;GdAB6^_R*DBGMLZ6${TCT6Y zSVD#9=ap7_v2D{eA^bzVfgpb@^E{=TD_K!KCXc=?BkJ#5zA@hqu7{s{kUoC!&C93C zJwgWx&9t@Ah(d7O2WRpy-HGK@W%k1xQc+^N+s(vNMo$}xbJzu8H_L3;yY1n< z9ycL`Hp?JKI_*kE8*uThmIZq%^QPqXw0 zdHHF~R)tbaf$%`PQI-J&II?YU%fwtgvt%Ma_9A|1D?qcurCDGDewKKbT6S-3NTAvy zyG5+ab2dW1kvI1yaRwQ}!_hGjkum3M7j9Z}w`##}0UG@rAler(y_8S9^n9ig$IDgj z?^v@zpT_o8%K(W#^R)Gn8`=BrtH&UjP+qL0jb=gi~##&0snlD>MEIozYR&Ol@&r7eGif+@0+Ni4hCFX92U)-+qc>%rl=4Q?c z4a}>j1?jo~{Jbu_Vm6dFcunfi_{Kl=*J>nfxu+|g?>*A~qiBreZT_wDAY3h3U$+wc z5;^2E_kHJm4fVX375BwXTqk7wdPE-?R52t&@+;LhFrTQcwXHV(8 z+NEurudb=|5@4jw0es%d3cQ5xV6*=N%(K&B0tS6{<}iqND3pg<_DG$nlL;ciu_$0q zdepi(HGyECNCM6zJz7p@07P)pMf#B~m@ysl`QOs>CHPV}dYx*0s!V+A-qT9cyKmoV zG5&Xn(Z=8y$=dzfk~h8%EDlEup2<{3eFEsZAf5(+#LNR3yqENKZ==iS`h$ztHScNl zus37?#iz&PPROh7E;~c+@tgfaf6K3SM@Bi3YcAZHbkR`5}^Cd>Y*u6LI zX+kRXsv)sgX!VLAo+?KBRYL$?)AgER;t%5m<`CJkeeIGVGgiy(sv$J{gzmf{^`^lO z(--oM`_}3?Lr6-S|AHYF=1D2nbb*&=Al!dhkTByk^^1mZm*B4J5uYc9xTlFjgcl6~ zY8{iV8YRJP?k?*gr`>~GHeoHt-CQq5bL0)v>S_0|6qS4kM zFL|vW1_y~iv<}`;4B^%ibRTjFFQ|)-AcdmV7^IktAVUn_6a$!{T$D;)na@ET`3qhW z$WX#XHqqNL2sEEjD6O!hZeQ(7edoXdJ&yAvZwu&cy5f`n67rvopz{NKN%9YLdq1*;KVI6Xy zrZpE5xG};G_A+PfAqL|qK_Y5dZj`~H1Udf<30k4c7zFO67Dh2GtCV9acaCaJsO`Nn zX)-CXU{~d2ZKd{O2{#-?C)VQLO6qUfN2pIBFZ>^nA&P_Nf2LiPMYRff2iFuIq@^&S z!6NObMZjAS_Q@i5N+MXonw3Y5)~|84m2B3slDbo7--&MQQY1(s?a8m36^y$Go!rHi zT6e%9+Qh?BWKAk2pQATp6F&q8B0_h-TM45hD35{Nt z-GoJd8eOy&xx4zKYexE=7;#WlKr$L&kVf!XxHB^YD}Y|S&aMIHoa5@+QzjohT2tuzjTBsx_HvMYK-B zUNGf0hHm~Sp7C_t7Ywo(W2nq6BtQCK{#=&7l@s_`u*om1fnV`pm)e|t=TmKmyDl{Q zZapO*?6Q};>@$uqP;Bl)e`-^|gY`~i+%bPMH|MRLTh2d_5o;f9)>Bxa)t=du3wp0i zxn&1)?d`|vXIhQpEIT#Gd_Zgd!k&MDzH;J!__tZ{Kxgb+_V$m#;5MvSIfHtRXXk_oN zc^Xw0-H3yqb%Cz^2XNrRVcsTnETYr8u^EZen;VJv%Ts=TUIgMVR)3g&fKC3mCsdMo zCClXJQD%m7Y2{~@;(&96Kf72QORe5r+;z74Uaz?lQHRfr_v?iF>5MXSK1L<GdDVxOgO$kZ5Hcl}SQp;0|*-qmSd^8@Nj!mzZ&#V>0&ex9OSroh4i4MdRoZ zlQY=94TsfkMr4#Q$hi&IoCK{WXf61!aADTL(+xg-3^hGoVx{;5WjX;9#v@G=s`LpV z4;#IhY#HXM5NPbffffESmh7E{Aji2*^u}uj?6Epb_P%^zrUH?#V}+;%!3p_$U_D$? zh6(6rWoVD%qngjVv-DDO^w1{p(3uEv?C8hw)nW3`tSZwc$Lu$8k#IllSO3nczV zIa?C>SS~}N06UKP32c=rNArN=fS>KwONk%KcV&oClm{eDl{eFy4T*N)FtW>L=5G^k zR1&6OMfFeGYht^)UkJas!<$TOdB8wIHBa3uOtq94FS@)yLK96sW*B)ug#%AnjHr;y z)RO`A8#?upk*Sm$4|9Z6(o_{@bLQ;}`*griFt>>XNpUqKJWN^AG&V~G+4%BblLt+y z7-Q0KND_@2L~(6 zj4DMe{QfFN97rL&1%AkY$)wVe6%DFdp&LZN!mvoLkOmUIGO{26eX2rmCB^n2tWdFl z5t4ga4E!L&1Ok{iF(SZ(M&XwQ4j1GYm{>BUkQEavqM|crU}*AiZ$+d9hnN+~p*Dam z!YDpgXq=IW3Gl(b0?r;jJ{)l9kd+At2adRZLkb;4F4k+Tsre z;=&n!lLr5}mSO}{s8Hiz4HuEA#(7g~MvSC@rXuhLOj$TkdU$bwm#wH&su^Lk;6sv$ z6Zb)Gf!gCLk0uiLAz)yuVE!Q}ACQQ}RL7GXE^rK}r7x_Y#kR0s&RdXVSZIRZG_2B9 zX!i(IUP6Q;Inoq&mZ*utbN+{gArW|8Fff8Hd!Mu9n`Z)%stD>s%nhot?oM49NKq<$ zcA?M>4t6y$up+69gBNb?nyt%IWv|g{YcW?sV;yTRBLoWvZ4U( zVua|)$3#c-_K_e}jnFdL3gi$x`Vv zLnnu@j}-4PQb=@QiGd7~ljwc$Fp^eMwkaAaVc>(~V&24uBoBAw50Ex?M@hjRg)hR& zRA32R<8<*955cbnGmjX;v%jjyBH0P3hpCLrkP{TbXRpk`iHjEl6Hp%;yzDqVG)s)X z3M5gz#{y=yW0WR_iRL&`uo4^1X!wtHI#69qDm=?J?;9Ei@d;U`YlHl4q>3^H~)YF6sGAoM5HFNz~qDyDULH8wVORfIVZez>5M zt&n;WxzBM5nn3^q#ZVjh798ppG#3>dTt6a~1ra_dSjsJ7Lk!8hdI;(dxVfI@D`04HEXG9QYP(Dcuw53{bH zpitn{j~x=tQ6eNb4e+#kn(&nL)bu^aM2?Yvd;pasQ1J9qIY_&=hW>@g1_WPwde(ev zgY8v|i-So9w-gT#i~u3%j>Q4QJBDKA2Fr6px0!*KSt5`H=I;4oTQrUd~Q2 z5q`_3g&-pIoJA8O`RlC!v`^p zXDTVcYzD2$Aaz1;dIH>J3<9_SgccAh!$=8CKr45dYYC?}$=Ml;8!G+#JY?+U^viLW zD=G-+hV8-2Gzl^QBz}lJm8U@zF(vu0hge6j-A8RXgdY+7rk8NQy~;G4Y0>E&*!4Yl zGTq*&s9Wojd-`;QIzM3vzd>Gg`>D;$;rHq_BLCm9`5&j}|3hs4Gwd+1a{TA#{9mF? z1|~+f|07ucW7tVqd1T-_<2mzeGx28AUUXxhu)`uGgbWcN!G=H-Ay7ePLExL`>RopWTq9P`_*Mxx35X3lH%$lgo5I zozBc;HkSqf0#H-{M>>wl8s%jX|RgaOQ?z#zz$0zmQOs0UerWLOxA zxDF?~xTP><76@7>lM0afc*PLp1I})+WfG#N1T@@CZiw-kAAF)2aaR~xsCLbdbATAa z6Y>ke7jlJI7yS<*1nJll*^oPqF)5z-evLu$g-$2b8F0`mL=oa`a>x+3ZKoV%&_XZX zc_+}Agt!;+3SXsdNLRxC->cNFy1^_zPTjyOoWve6Jbb81wP2`gfD85Nmcws%=X8WG z#~%$FwF}Y(3{E$F8@(_O%)@*{51gR?jOVb{2bQ`6)=M+Q)twMVOas(`*E0OsoFE^1 znmRFVsFNka6#@(l0pu6TLN!4E2lUw;swR7c>k-%HTF4`G<}Xf%oHQL+N@V>N$Y3`F zIDOKMlZG$kbdVCI{58IQGhQLLFS=KeUXYLOBYWWC0cM|0<_)=QGx(4MfntjkVaTB~1_TAO6La6h4j}<~+6ZJl= za@>zM5SdWM1IaEz0Q=?i6LZQo^;2qVW2e1cx7E;aF|mC+0q>K$T}C(fHaWu(F-34H zx@9%Aedno|K)85WD{W549`$hZ~2|?FyQHx8&w+Q%w99mmU+jhXF z+j8`_?}y{u;e@_<5zi3d3k6)_ZVf#;*LG~2^PW?0^>@yQIIE-r84xdWPc-!A@`#&`#KzG z6=sA6?7M=TeidH14$aY>Htvt>15xeBk5+X=pveWun0Qu{fAk43Fh0KHPt?sNaz@uq z+O}gxkA7_1%wt=WhS;rW??!k%{@xO#Pvjly#xd(&p9kB+2ki`a)FNcX&>J?_rhSgx z5c!aM>-vd_d!}QWPc?n(fp;x3~#@RQzz`r-*)*esUTGP$wnS7G2Ssy^5au6N`O&zDb47aYC{!1kTG zBR_YDmHh}yAN?KBP(G)f(Iu{4eg*fsDdg(N7l9-Go1laEQ-j`RfySjpZk^#WD8AB% zXv^o9%0B9WQ|?r7@5B)mLHBe71tqrF~-du zVlGIy!=C?8i%eL(x4c#ps)>Ywtw_`WVga6t?$(G=n&B8D~yJmIX+ zH}D`~h%5v+!tVsv&<)3HbOiQCZve0_o-w+SjX%x-XUN>vCLI{xTK3naN|uspg8@YUED%{)BDG_|9_7(C=UMU#4-q<_2F@YVSVC1)dIqB z|6-3ZHuuOn(u7NNXrDh2K7CE{8~i#e`fdK^)w(2t2mC#G7u%gIsP+5m|5P!@c03td zHUzONoEXss4f2isL#C!Y@Mum79I&qlCZRc7Qn${2s4<{ED$1FL32d&Lnv8-taV%)l z*a6y~DseWagc<9xu-fF|Dz>oTDunj~T#-2nZUcZ{bq5cKdet0y4|8(RH($$VX6g6v z>CnAFzi`KLJSm3t;E4Gy_XPAmsu}5Ln6l_;TiWJ4C+29b{Y4=Rsd?J0B8Fl=9!R=5O%Ay^9H*NCB6{;pa%k7 zStA};xZ>4TgH|!VAr5pyywJeeMIWWe)4anywcABTpTa&k_c*8BX5As5^7X^+0T%tm z1*p0Im=3)W|H6KN$JHY}5Lb7iIPj-VAWSv<@TYhYC>D1R zY5oZE)m)@<4wbqngHtCaRZPR0Bs8mR6!4Vu6x^upD8f_tk?iHahiqNOI*@lH?ee>e zxyN$PZcpi)-W<}q%Dd6K@ty5m`7aWA7x1#lCyt(6o?%|L&o$3B(nOL|NQ{$6Dak0= zCQ?)KQZkpxEmamfOtNgGUOfh+d%5Z-tY5f#9(Gae!R`Cn$GJze&vXxUZ+nNlzY*{H~}UKYV$8SNa(H9{MKsruMk|?%bmCE?x5S-1G08`p$jg zpGeQ7=iqttJ^cglRs$pn`~?w8#~$9wr$(C zH@0otHh!^f+qQG^f8TR=?#{(L{q)q-)YR0}e5Y!<`$M%LN5`Zp>k{ue%D%8+S=!c* z)M#k~X>lcUDld}i2W!Au-(v8MyGTjrJ*%gC%xk}q{-z%Y;Ou+E~g;Q3}5%ahs|LEh`GmDOs_v6F=o@#pA zy}RgeXbG>hjib5^p+~j~TK!b>ft`ZWDX}{MPhTjaGh>1%VRRzdgL>I61J(qL(f<^{ zMEnf~tw$^67iLHEVD?&hXf_T=ZqbZx@|wBByI!rYGZq|Apn>1N=BA0zOm5s*Q<#qj zzi~$GRTb^8Im7S3)!$56#@BSXh@or2%?K$nMO-4R6mP#^Ev`?H?hX?duxuap4Hg$@fj&%SP~8n$WWe&S^b2Pv>~(L)4Q^z} z^DZ|Vgls>^3)u!JJ^=iNVC@X@1yQA!xYTd-FKU%5)~kLtPxRL4%e?L1Y#(8MCQN| z(z`$Ip;kj`4}WJ7Xu=3N?jYPzrh2ar=G|9r6P_VNC1f!(;=w=;M>7<``3g;w1$QJyx2yu52WwV+%q}@=lY zmj#I^Qk{w!mPQ#R;8>KvofXzGMGwt`q>KGfc&>Bt(uFwUw9=*S%%C0>@TH5)nX*h~ zq}ns9W2NiUzNj||H%T{XX%bY#DvFrrxaT;`3m*PtXU0g%ig6Z}6lTrQn#VM%ddPUl zdnkJ-dZ2qhdY{HVggvA^6g@;a32;z+Qh!E#MSKbP7L}aZKEQltWf%F*I6iP$|A1E$ z3;XATcJlEIV;X3zsMd$F_10J6o`^l6WRtR}dtK_lxYAr3xGylDsIwV8S9!Qf;A@Xp z?k_->CUlJG>i&n_^MvJ@)dkoxvaM@b<-E{-a(%+%9N99(uZ3R~y+C+R{roLkCc9E{ z0l_)OW0=!Gw?h9zTnqP}jBc=9X}REh;^qYS=<(L)tcX9!J~46*ehvEQ_tei{u--AV zPfgWsuc|+Ra}H1S)0*W{iSu0eneG^O;xhNyxPjUPp}(H1%<-AmD$&Bm2aER?ofkrx zhccn~5N*$zd~rZq1ty%N#q@6$Vl2fXF{Eq+9S?FF;W5Sa#3dZ$Sb=4X+UPeFN|*;a zbvXqn4JOyJTj6zNO&UwCdNoJ>RLZYNG>2~jYz&_37cXR*!DJ6Q2GTx$tp~DC3Z|}F z>0$po!D)*PA-9D-N@vv0aXv{e9dl58>)3-Jj1IXeLAX|lsg=h6c%q+c&Zc4^V38m}$6)8U$5_FEVA?++VXgP@HHkmby;L@dm%*v%4fbQ?xfr zu9-ipFM0L!UULD0!Eo3<+QNsS=eDKo_C}B77(Wc2)>~xKC8=~SJOGiP?rC;sif>^lX&uJRQ#7r#nA| ze|~6rCMI=7L@{=@dzNtl-=dLrVU0r!pp4bgPS-kx%S3VV<*z|)#QQ<)p zDO_IRl4o$*8(*I(;Mk}pHmf?+*soQ>)k(@LzUyT;}I470CIm z9L#M2nL1FU{Q2YV8H0sQj;}P^!8EmBHgSgVr)!8f29D2R`X9Zblb?=3`oB4 z#bv4e{oYv8_=evRKPq~Q$4f&!(lDRS8BK+bda&ZQpTq_0Kvcu*Iu#Z0flKDxq-vik zs?K)n!B%InM?7b)_*EL0{b8ZSM*2z;5C+%wJOcLlHZ})XUDqa6hzhYM@Sy!@Qh~(2 ztGfQ-&TEuR&$_$0q)Lc3siRt52h0#e-n_0lE*Pry2}WUJ7q_v|0wD|(y6 zLPE^tg(Th~=b$9Rb_3RrGU5?bj0jEz#ZfZf_C^z> zLXq2apWiYLoaU^|R<;Gd)6)DRVQdJ?by`cv4dLalG8gr2kT2u4EFFjh9{JhNT$GGk ztdx3&`&r$T6Yxb03FKHy3AJ4MPg=olOifk5%$RM>Z7jnuhcwUDG}cxt5@zFFBP4aD z_%1EKO_^5nJ!LP@{xpBjaM5mT*@`*z@DovPbg8O{I<4EDkQCW@ERKK3s z=|HzB(NbM`slC~pC!ObAu?KAIs7CA+#Rf?^$;d z!+O7edr6N8x;q2g;jUk(U^Grs#~{fDm@q1#n4h8brjG>{2{DF0^Pd|N>5KE=R061_#MT1-em!b_^2k10og4@?fnL z&z=KO@z1$C5oweZTlWA8(r0!lJSDwB9p{ztD+V4ia{_F?8Y8m;k(`cZp^y**Zs0+k zdArcby`NqO<{dsZU0Vt~s}&Y?iGLUa#tH_4krnPePBAAgtoWeE=d-y}`=#=>Bi6LD zV%)XN|I~dMfpe^_3u-KB6JH9MM)-3}7Y4;>j_~tSR0x}pTFgmoCM7n~lgI}p5Nja%OMwkmU8+x)kR_bPMMh13q#w) ziQ*8nxpkbQ#t7w;e_4%k5xsi&R0vj+2oVv*F)g*8IgxXIy3zSl5Ooc3ROn13XNvp7 zgg|bupY~7Q0SFLr%wLWBM!-GroYEa)9K9XA<4W{$t&d|-432G&=8OFKZ{LvQ_5mBm ziue(a3{Jw-kvvXHmjC&V;Lgi*cV^G))2a+ zuFc3qGy<({VC}FlL z^p5HYkj?yq`WuXs@WzVX4j~AZ63(TS9GgcwP+!rV)iTf<(P}?Bi)jx7fFr=vBM*Wvf_)@@*sG6iIQ>{OrI8}96O@sh0&pD`a z)U*aa5Rdd_RSAf)lx>I~1j?{lU@b--+%B5li`G9o#=dO?wD9y*2uFk?{g+bVXpMva z1Rx)Q(=_s*_!p>5@PPX{DiMh?JN!t~PN_(=#le390325R7v!UH4oChI{{kHLvAF*P zRFvHzkw~;{=Y}W*pZ#NG7ahmv;D5b;f6RX#IvVF-g#SN5#9<$a3;0in*c@Vsz}X!@ zLa^C4Ms`KuT6XM+{@0`7*c|+LO}o_C{y-ekgTIh)$PWMO0s2Vqj6YY1BN%NsVh_U} z4f%f~!7x4Gzx*e1*rqtu9w-~Jhf;&b9p>u&Z~Rk_gYsYg6C!-Ddt>k-z9Vd>wi8qjS`Xk0OzI_@lds+^y|A01?`}#-&#F0%v?77OF-2?Be2t3 zK#v)_)_W{GmAPT5pP1!}$!tq)q%)ik6EOP5CAk=9%?tA8hrj5_NnfJ{9Cs6)H~X!rQV^Bbtc8TN+<6SK<6&|F z#y!0>A5R@ASY_R=r#coprS%4M6-7f^0q)!tG*tsO!@{hMCcj+*l%i`h+R0koStH4~ z-B~vZ?0E6(4SC^3S7a-|b54%u&xCnJ+z)_Z;-G5uu~NtLW`+MA{rJuBX&T@gqSz)pq!n z8k+c)SWlxLJs+6Mj0sFG=?y2wU;YBl4%hY&i+;+EWE{ZG1)Epe5l2Eg<~PbQc#LuT z^83J!@xw`AXe24^uFf8>vgx`I!d0r<%V;D7%9`}gcOmC`mHLY)9e1Xq80A~vz4_Ni zS1Z}X+kDQd?-QfxRHpg|J((jKUk~3GUpSHwMtPy_1e$b1U7vdz842 zk8j#$>Lz>St!x=UG$3-(N zUmI>kfC)(tp38hAq9Ig?84pgnc19x zXo0%+6XltXR^?r;bc5*xG>`+1-C)pqe_imx3;`fdu}fh#;Vsm_kIN6BTb6uiFIJ_Z=3F1Z^Z82P8(X+ok!@i~#E?8W$o5s3#>ULgd z{>|9BH|@WZ1PZUmAr7``0MVdvI$BRQl*je(AYaw8zu7F=_Q%-HL~I+_-i;WIyCRWIc2_A-PUYA+6#x|FbsI zT(x=X_NwAS^~I0Vjm{J=1^xkR{Egs)s6l0 zp2z5pibkc>R~dsF0OpX0XGk|+v4c=XD$}f-PTC{PUHiLWR>R}p6&bfaD@}g?DJkR# ztJ{Hhz>^R1h9qf%dTl@^@#ctt0v}qx=mIG$fn+ZE1B9`V;9I=nKcrq7E9#AxoNYz2 zAUhNp5PSoVC)P+==~_J32k{qwE}-L3oHxjVjKEnl6p=K}nJrszyvOpHX|w`f4tjqn z`K^G>j|RnZ;39C%UnM*6Yy{VW5ocj>4;^~2o}u2jln};wA$chPmWPfoJ7$jwRx^@m z{j?MtKG|gh>QUhu+7QIQXlMot9aJa~)=-;h>G#a zo_E1tvP$i3GiQMA)UgfFcHe0|`&_fdwz zuy@?qq{!>`!-sZ0EO1Kj&)kWhw){EWu+0b3>oA%acSN;X{G0~YUBGYl#m?z1m?Ft6pag0Qi_ zg6@_B1wwZI$*F1(afg^nL7bo8K7$L!{ObN&&f z104-V5c=l$^+z@LnHvQx>6*6}^Vg)tuglZFLRByUS9RY!7)!x`7r1|r$IYEU2vG

2^svq`#;l1 zA57ZAecMXI3R$BQ!#0F$y;Q2=AWxWTbCiC83RHY}V5Ua2ssE!tTDT(86z7(SVN^wZ zJZ9KA7!)q4kU?`9^P`Lzt}Nwn6{i?U@B&<4`TAnzu@9@?t!?EcvZmbGQpy7BNglGk znnvxi_y!v=2px};CXn=G6lBcP!AD@?cL)}lStAZ{e3Si8wrd@Ysu!Z zvG{DJoL3+pn4BOU?VftoZbxxC&AgVBQBc~VrEqVf9x5+I5f)IM%MmoX!3=qBK22C2 z0u7KSzFOB6Gnhnxs0YRW_f{4_7d!g z6JeMUi~Pb6?XC+ed`+NM*BE_i&}aLAVIwA~W4|TlA#KtTj-*VX6eY=9+?>T|F%wgB zWo*4GZ+()<0a4O4S&ZCI!*TSlh*nZoMpV}ywfURTyWhNiQ6Ez=V^x26=%PP3u*mpw zB?~A392x93!b;VT8vCNX9uXPO2M68q7#Kdcx&;emt z-(pEyn4Za?%G-CD<=?&Lf6O}L?L8g1l@43k2dpO=<`Avo&T@WY3L?MptAY)c|%S9KSaaa8DeeXon~ zF1-i7?O;m209bvs)n#ZEt2MB^Sv7pg!2tH-*CEg=}yyz zq&H&wX@FG=JKTf~zQf;gXgRbOx=9`NT^3=!Ucq7oCt)Fj9285d2~={mXM@tx(vnuh zlZB-m8ybokeJAQ>lozn#`MB`tLT?-laPfAXp?l`P485L1f?wFf}p=m?>5P@ ztS)7Sn{D>$xqX@+X{%RfPdumduG@BNdnzprdIomO+s@kB*B2$7TGmqr%uza!OzSlF z<|fRe8&?{IoO4RgSLG%{OT{ltntSOvpD_Cwirm*4acAh?rVwz~#$*;W5Jj?8Dp*Bs8Q1_jM{*Ir*uu*qVfWj&$%9YH{ z5cc{zWWiW>bBhdVe|)JGBUUR|G5$;q&`d&D ztoIE5%Ag`3%j3vn$)n|$`HM-%(*7dZ&*zB}nz|7r_76!vwPAO+cb~65AwT-H%}-tr zmt6oR0Nonjd;6cbZTHK_R9lzL#eAF2mg&7$v!l9V6npU_{zZ|V^YARmA@ZX}W-9}e zvbfNBq#sk{9CQjP_5B9?vnJ~*%W6`F6!hi&hGdf@?6~xJ71iE?g#QpWwyAWjN9NY( z6KHNWY@q_PymNwu&&9{U>s3-Y>Je(4-Ix{@NIfp`wtEYyVGpqw)9=lZG51&{@XeWca>mmn z-Y+NqB!{xlXk}bw!uX0fi&%?ji+Bg`O|d2MGisGRau_VMIk9ty4^MJ<&yHW3Y@%t$ zwB(}--kv6;6))|K?j;OZ)uH>dd*Mv;MTnH%tzA$mS(MMYH2=d4sA-Wrh>YF^m-3@) z>Lqne*I;c**}l10`&lvqT4FioQWE2Yv*!KZp`c?fSMLu~WZ$4gm0;UExBo?7YbGeO zq9<^U3NiRqLQX;0c==p1`PLlU!e!fwCQu0bU2;^kutBu%9{N<@OQX&z^-OjKETTE+*zed$8=%vQTj_ z$$z~c7E_u7Y$2G`p*IK0BO&{YH(atx6B%0iQGDeGM?m~wk8S!vDMRZzKS|JsQwcS^7SBhs(6dXN7cy zhDd_$>CbxbVPfxMC~_QQHKPQ9Y|5-6PhGwh_fEKw~QsCRJ2`euN;|I}c<7nOW zl?4Gr3n0(TBgAQ`&B*WoU#9X$CJKUv8Nv3ZFUsqmaVa>ud*3QH1dg6*HTa zNVtpV*kfpmG!wbUEW@vYK4hhs2uNBI+5&h6b5`kj2XhjG;Y|g;9ukvL(iM_>r&DkO z=)cQBfFX+m`TFo7sR{CakN)Oe3Z?_^=lXJUtQ*ER?F%7d*$!Y0H ziD`y!o@0}8G1ZwK-|Pr|zmG{)@luwBNsD`YyduukGjpJUxGRH$j+f$&RD+#%Vi*rV zX&%5-{!kOYoFj>OPd92>IT)tkD?m|7)LdqnhUe=#cYbe;p-zKkT(y3#y#SZA zS61r3hAw{YbzQyicXfdC)*f&uM6|>$;?PEL>rIIFw#~{5U)_ zraPddnY8wcE)=nCN3UT4?*I-^U#3V*wFbM}gS%V$fpqt`O=JLm*yVxb_DqgL2uzpd zyJJRmPWI=ACcGE1=1yw zIU9xUYE7x`d#o}gS81s-#{v<9o<=y3Fa^^Lxakq6hQRz+v<@sqk%pJ`hT*fZsx=;L6KQj$=DB8{MQv?R%@k5iE6veY}u#cB# zDxVsz`YMqe)lJMrY9+KDS(H>pb|Z5HDGIMi*LFfQNc4mIK3?)x{Oi`aqCdxk#^nV$ z>ZSM3{?2Tu@4t@vR^^QbYI2Y#gi~afdLe(rVOVQz`q)reA84>%Fi8URcEFT;u6)5I zK{!da!R_w-*gjXJQrIc1&qBPPti{g!{^hm~HIKGVSeT^=De0Ws)NxoLRyhMCtG#P^ z4P%RaEEkK-uh|W7rSPn5qG(z&7ck$>u3$A@E^%s!3f*>RrD?>nIq7~|a!NuYyQfe1 z>$Y>o#-NFvbHFiL^_gHXwc19-GBqA6&WA0lzKCT2=@0o%9LzCe<48i~ICk*@6g;Xf zt!&YN5j#Ihk3s034qctu!Vm=$6SXhm&o)Grz=Q}kc>Qnxti4je_wM`PsBCre#^5 zQLj4=&Iu{kCqMa+sD3q~FCl=9BPK-K2`(uVir$@m)D2SH#fox+?YLOITZg;)>;nJ} zxMrc(eVro_RM4SEg*$szA8%05-F*k}S88O&tr9Qma?|On12>&KG-V~9opaE$0=T5d z2;7-u?-FzxQj$L#L5Z+Vf#feZLkCmIZXehxaDTW4KF*W`~{T z7ATRZVR$ffO@n*J?6~d)ET(}R4+!J-3JAhij4JDv+{g-JrYlE>RzQ?pR!wX}QrkRk?DGoA>&%`~_dC7bcq9mF}DH{8@W~q?{3aU&b8;yzvMwCk zF^gFK4xM{j!0O>7@hu4sWD*J(6B=BPSGZf|IKypRvLEC3bwL1E^KQmKOs}=|hG9n) zG{}6~tM#}A!*PcMDPxR_5546|;SA$wir^BUVbF+tF=1L4!U{M_r;BC^N-!F}I~K9V zN=5jX;yx*_H+Scsn9%H5-6(N+O<(FTE2$#lNU2s^dqbwhx_riSY9LDb^A^X9ALLILbXTjk=gqvP82ys}o2@K^>9mOBkcGc|cN9ZgZ6l|i zN{FA%JR51Jtt?P$&2^&~Mi~t@F%fahL%iCp$w4CX46fB1f3-}#{wQe`L?^}})QPw$ zaeO^+NLv~OYE$PS|HknFGN{vvL1lm<(x)VNg=iE9mU*&`7do1Po~F{V{Zk9UxB71@ zQ= z1BI3Gh^Za+puC5P$nuGN=p*uK^xY1x12}OLlRG;Jh5~MSuC}HKlw}*_qqH@fY;eaAYzz*om6sfJLL&S# zwFT3~w?9S3@R28S%|?cn&{wP48v*vyR<*0wa>DBcqNQZz>c~W`n#BsvWw(LiDuEs8 z%3xjj5#fC(5lA_(fgO9W2;i?&u8ShCPGHr#opmjqRneJ!FJ5KKP|}|n`{7h-o-Rji z1JXh9Gw$lfHyw%mLEfgO`M2X0JeBRfxe=s1?A4a9@xi$fuPm2W%=V*`8>--jTh6@I zN-^qh-d{Z13D*lfqThJ^IwjlV3ww!KSQ_S7c-$K*r>!wIaxA6S^UUUOvqX3DlLVWt zcdld49`h=X>kqhx#*da9?$I7W^+GghL8v0DZQ+$t#xzQUD#MFIvupw2wBi+}nY(nc zK?#%1q19#dQNvmdLjVZ)<3dV~ZU1q93@T!>qL)te!7BijChKR!0r>XE>DA6PRhZ{z zqxt!gbVhSF=_Y?-5@6Z)OZDcEB+nY&dR?W*e4V9xOBq0M!J}0Jd4Kl)iQ6Ia99Ep{ zRVJs4k%g~H`MCHTMf=keC&Q?Pxs`33Bmaq5BJv%E8rBqkyzHZ?lsIZu>$yZG;5rqC~T>D>W6kcf(=&z^fjJZO*aPym14xjAr|a$)bblYwr{F`umDlmr;k{ z1}bGt@dCO+M>w&wgzS=pX{El!V3KdcU_EPJbi3VYBJi-a>CiAB2V672)ITQ?){>jx z(t6`oZ+}VDE!vyA+MB6*69)1ox2IP@=>G9_DgVXNtgL&%L%OqlNf>E?{^>)aO~EtE z$IYpzi^0XM5A8Dfr6Ha1ZM%wt5o50vNlghxRlq56%uE5tEl#Yla6f9ylm&axy=O>B zs;>G5$oDA!F{j(=HI0!Fsx;2BalgRwf_dr|vh;S}N}fULv=?D41+ZN(wx{y!Qq^oh06Yzxg4icl=xSe!>Cs zpVA)}-wfVMo-{KUqfjoLY$D0kWT<{2!Kxs0=8q)Nb`kWsS%XbJ+(qVD_b;a;C~mF% za28l5(w`BrD*a4#5i}VzPKCyG#wCZjLF!UFMDex1B+kXS^9Iamw4DD_M=4>bjy!E7x{b6K;e=GyLKfP+gPvlCmI%uWE({P;~f~PDO#^Q)=>0 z!vyr|vhBo4B-S(A84nV3qT=CX;w7fkXoQ&52e(?yv)Rg0gekc4GIvLg7qY$gNMdle z2@ZYb;<)U>#C(zzWQ%UMN8AP0D+vg`G=CZ5vkVfGiiB4`*cxf3uZHa}eQ*}8uC-!q zk82oLKewE`c3MR`Bs&ZHFZvBWF>a&k3b|NNBn`*&m0e;cuf?ZYUiY)pN$eg=OPBN%ezY^~z44Ks&2w6Ods$1+}vBUW8Kyj<_aM(R+w`S`%JgMS?q0VnJ;E zYrP-amDGIiCcT>~wYUUiWxLP)R8A<}b(poGNl}({xrziFKVE#ApLUGl zoZ(c&22lls4px;wiAyyTe)&S?2767zvJ^U#XwguK&OXLxizPaWh`CK$&;Y}B-Oh|A z#<2T*|CM;)9h62)(1)FW_g?m4aYoX|RvBCq z>&4b7a)o2vq+*YNS=jQf19J94j-r&saRFOp#jzu&3gME@CJEH9B$dCq%EcTqg zRpCIWVz5|E#QNimr|Pc+VRm!2jYS8NJmqx?<#9=+rDFGG8!Ngp+Tb zemHLO|IqKsU?qO)$#^BWZE}4YzEXAOBs{LKw9v=5=j*)+!6~xn5(W6x&fxISD5|*D|#DUX7N4i@uUf6-9jN#sWr`~ zQLmI=axa3-`na$?ep@v7bP7`V#&S|9`KV~pX~ehBBKZ~c9xQQLK9Y&@Io4voU8Q8C zoFfNnhW4ax4kNG|NOM)c0Q}BRHAar|j0~pp?VxHARf*x|g1z|JBz! z3HHnLOViVZa3yiFNHHSRzAa(6+YP_|1nX*JTSK`V2pN>8c696m9)?wei&4g;d8lF# zOJywzN6&*LW^_v?23LK!duq3_f6WD&%FYtqJ~8T6`TbcGbXA4rtsMVa>Z3yPZF4Tx z{HyP~1C39Pi#>`R{uD8fqV#Y&@6va-eCIo)HWyRv5T39R2JZD8qUxWZy^tAdXa0eZ z8S{&hGor?Cs>gN0#^f$ylOrdTNR|T;Gu%kl4eY$fF`GlDK$c$EV8_dE??Hq88whDW zmM^qCiocTA5H$SYk|_|Ae3lf^GYlux%R*+Lr?ZF}panGZgpFAF1=c?&?y|;2)Q}X# zgcm*`KQYR{%X5x3t%#ZNFb_P`3uNIFT@U&2r7=VrGpRi%VhT{>L~M+F)bO+jG|_7q zhSUIf4EiYl_&Qgl#_;P1Z@s{wuG(j$Myzw^CwjzmzkZ*rT;{k>*!kW6ukEl=<>c%L zV$zvedq!T(wyq{(1_tSp3Zx2|!#r|Ei%b*LddIhR{>vxO1i{u#HOd1>VKdt48vkg3M@2to9%d`xggcms8Lg~ajduQbIK<>T9f}iF3()WpL=?+TtLO}-7H_mE5!2al zo#2eRL1qgP6aF!%OaT%hu!6P5 z;V<8iecmSe+PVX~uN?dYUq^f@fn%smBqQ{ow|WO@Kf}qcBKjBDRnRwY1I~h!`i3sBnnfOe3Dm4N#)nq~?QQnB$rj!t2_E3$`}wFL&g1s`}@4 z(74fIl|3RCYbdiNDlh6QSS65CHxjhXSLrU}4u_%sZXfU=*^dCEOrSqm(=>M4TCmP zX4}rRo-5mJbk1ha#p;Mu9SAXfRf>brk^Z3mywuG2u90S4dgi;U!%)_XP;_bA-Qmq` z?B86oJQ8LGCPr{qg8DR5Xdl){Em1mDR%Yd)dDivLA1$qGm+{)?6okiWmAH6mWi(@V zvw_A`MxDn5ij5J+MLnL_m7^a_uZ~Egs|X! zLi#I#N~jnPm}wB!L}(w@mjSQOTXuxwFWxHcO`8oNlr2h6!6L02kjOwGehHU^couQQ z&{$Fui-cUGfCVZh%js&-I_|3pHWPPXJWyKqUjwkRecHPJEV~`Kp4L_yixg0+mN^~2 zrd_9S-boonY>}D!O7=s^_}9$rz}g{YVhz3Eb`dvo43l1B?7ac!qH1jeNoPBlGSNgI zS$AhfwLGoUIgmuln|_wF)yrtht5gC$DUY>SId2<`p?=1J#8R6t^G@Gjii%s>|GKSy z{`%op8LBc|*MFDE5iArXb9Y>!ay*#+44ud^C1_}vXLu6Whgv^XD|#wFHGB?!M# z;Zt~a)e|1BJSmM|6iwk?9ygO7Y}PSTvsLIhurkfb?OhiSjY^tsbGSejWE24vO`WBk zetx;hjt&&zUJ}FNQTApS-FTI$*39T^-MG*kuzr|%^CTAMj5%&=XY6rDIrYtK+ORI2 zuG3k_lw0*KHHt{~Dt<0&t%|;>ezN8Kab8Xd%#*C$9h2AM-Z(G}`QDx0W-?W9FCAoF z`&67wLo{XKLk0e%(wP!J%G*CLi}Q8{JFUN%%Q@P``5x8z0|`rIeJgPwvSp5OZcLuH zA7D^DZyinTL_J^iRPTtqD-Zs%nr#sa3R~T;v?89NaQwG836vG@8IT2_l@akfXYsM# zOEUz|sRH6Q)rHLlAEUXjF*GIHxQ6R4^dMb%9#l%B;!g!nPDB$9rPvQ*?vdG zw?ForXXkg7ZR7Zv;`T^UD%e7jLE9?0Yu9LO?Fuorm^z)EgF*7hMQ=gDTNxZ~`Y&C@8-ZqW>je!@b2ULY&xAo? z&y`+0k&U_SRt+g+2yIBBMw&L92`j8*Zrl|iN<6id$y}UDCi7rteQSGrZQyWM45zTH zb>@D6(>^b=o|Te^sZA9VvJQHeIJLIglu$)x`X1I^uSrT6gtDwI4YNW>!GvLXDuO&t zMFbCK>U;z;XGBZSmS954%#5NkT)8llaS1YQSU|uBlSBYqo&&vmBB}JJxv;v{!eFoX zT}c4^g(T8^$*ahr2DOxEBhiH(2?xospfb>Ni}IAw3Qw!Zr7fC|HbQ_P(!DUZ5U!9iqh?Vvv zqL*Gd--)uipiC#p$O=>bV{oF^SS(O+>)cDGS~IBAvxVQ>0RnR$f|rp!rRM}EKPiUg zEZ2{FsaovNUkSmrno@NHxY&<4I828VN$MboIZfZZ0IWjRV!4~q{Nq6MFQO5C^b=nA zw5+ocEXfHp=_+yjJmmH_DJk0ObYT~1&UUc38zVRjD#CCn@abaF3%|#0T54wa{i@YK zaoIu{CD7{0g_ncF|6%MLgENWSE+5;r%}FvbCw4N)#GE7(+qq-g<{jI%ZQHiZ&2DYg z?z>Ob*88cezjRlf?)vLG*ZFzayZ+$j7DPW9+8r3#nOQlA<#Khlb94kaa%gmQd3nc? z;L4TLvQXe$_Sj6hnO^+5NNq_JtJUX+@D7`IB*iMEm<;&=cQ(pr{0@rggF%j8Jl)MU ze6UU@_DmR>tS>{+ZCLonWBZ=pkj4npSCMoh?AB0a-!}?3x2rxJ&E_yn3I~Ug7!7V* zK?Zr|$6PiNFA~3Pm+%#~s+BszW zb2Ft!V86$4^ZM!c)Rm)r3CAid{F4}mlH7Y4^OQ2b1(r^!WPMy+m^Q|^%PsLqwNEoo zjv*Q@?aKt+tR^m@!^#cv2vO8S9ojQ$bc_OQmML+o`dj1Drb8oJR;P#R7>xdeQnjfs zJ*8^e6__E7hw~)Y?A$>D5B-Of0zSUEk#;c+=_1N~EU3KAP4qL3Oqm zfTmDUPJ>1B3kOmP+c*r4Uu22Z^alUWccP_cn9NASj;-+pzlZ<1-EN&@#*4PGc}5G6 z;vp23@lzp4xFbeKc_pMiSp|Ghvx61E?l|0O?9C-ys^ z>jy&6)dxoBIg_-!{HidFBd)LZd3P>|6bI5W-sJCNHBAg~0~nH3{bV5;K=tKHPXRvGBecfI?TGWoxs5|-9wNA3NrZ|d5=(`a&JB5~Cf*%1!J_e#x6h=8q zil9p)db2(kto7SH0{rsHNfe!}P;W3+5 z8Grd#5I=rBlaT!;x8G|#ibBV1@+peTzq(xF*(%Sw0P~UO zEViNVQyr4+BM>jz_&27msza*W;hREvr(oR`b$|Voo5{*E7vi2OqG2v7#>%B?jAWYo z?hgnzZ7st&^G}#!)a)ZJ!dg-?-$_=+{KCC7nR9!GT?gi1jZ_&~=b1l3IBtkzS3{Sq zdO|l;JLqG#?q_Zw6097s>3<>e0KM_1{Cy}qz&1Qd-#zg>(41$)MvszrM0i^^-g2&* zeDF?Yeu(9d}>!Twu%F9d_Sv)dv=SX z*75=?29E&`qjHialzyb;QJB@XS+Vdw_8k}tDVnohC(-)n@HW*&s{-+eYkP#+--(ozW)zgr^}qdaG~5i{?wf%F@P%r~a%{hV=016e zI$E-B#I&b3Qri?18c{EPV%@uwEJ1t35-xoOMdfgc_uUNS?R4t0ij=Ze$k5t{6Q8z!D-pVjgy9GESJ6Sx8q*mW$IlI*&I++h(iNgTn#d9+{D{L+uNwQ2tW=dO`W?p93K*b24TAYGq#@^+Z_@1Mb7 zNSXJ>0}r{tS&C?FYG1i{#l==)3+d>(uVjoulA!*LfXn<&a@EzgLAX%RsmX7nn(|k$ zw@cCI>Ha6B)m9j39lulL-rC3Yo%Q;Y{Bb%Y@1n$w+~F0)=LX1@we9Vb#IuWiOXx{E zbp!O970aV*%axzWu~@gaFt#z(i}dSdC#e$|zP;v&JuGDM@86#efA$4J9Lhj>9u1*t zNj;4>0v}vTT2V~$O-Ra^T@0}(%Ksc`H@pGG{m;!{tLqS)lQ897_Hi$gw(;`ED4O%K zL2u1jWU3EZ7ol7dXX#Bs24BvfQ*LO-K8@NJzx!Ci?&60hhF8>Ie@||#4+$)`@P=`m zu6x2BU3OD%qFcr^cUs@!L*UKDoQ<@r60u4OiW)HurQEO$FR<0ymE? zKGrj3xY(5IK%jLf=eGg8iS`|GF0D<9_}lWKZIyj@pl_*ig;&=SW+k~(*}M*ibh-_d z`$$InjY8I){zpdyMF#747}^rLkNmGl`}m+sLR;qRm_lD>NM4bTRUwD zXJLXl`)We++b?w6--Hk3rx7&habs6<+rMf?V<+w*c9hI{%zK^UU~eoHqYEbpOIN1w zo3Ly4yeLBt`wN<8{t(A%-O1Ar=RaO^eCQH7pv|ZaG#MYpQq)N|kUgpNNi8e!`ZMWU7tnp`_lvIVBJJu}Om0kT-!z+ihy^R|mu z^o`~+L1s5zVn-}K;w}D%^E3o9WwdllVXMM1oW{e?mT9x_*s42Mt(Fzzew5Zu)W4{} ze3N%Sma*Una!qD@rF+2wcT;hXx9#~Z-i-S;LA*@X8p}Bia{09TV!fnX4|j&#dYL^0 z5)GE?1MXu2!VJ<0=_+(9qjCIElWu0;=neTw-V%J`=HH`%8)j8>><&PXIWrjO|XiRsXUgzeobWn`v%ejdylXwL%X-yi%L|s&Z+vG*M>kW~2Of}B220fzD^BO|=KyE$ zH?p+>Ya{S;DrdH~z>U$)e&;&o@9Ze%6{~Y+XXv*0je*YI&e6`nsU!Lq$ai69&i5)4 zo7%o}#z&4fn@9IYB=_VGU&LDSa~k*Pj;W}cJw|oMj zOSc}n-Tdt_3Wr5EWCDR3Lqz|!46xdOp2;8VHrr>#@vSsMR66o%iZKA&+@Ez=2sJ?5 zbI?bUHz@ZI{_)3Jn_Bl}@^hPIf!xzlIXQgJa=j*JIm5nz2d4)1GBp)F#~n!esVdEy zceW$OvT}8G0hh;ehjuoZ@FF={o{DM`ZzWypsI`Gq@dKyLSLeSI)=k=V9f*kV2X$)D zzoy9QwZQUUXZVs#)hrEuny_>64%w=%Wh~&zS4fuUC&G`3WvZ!9Eq))!TdftUZa2*X z)&d@qdY9w1*|Ru(}b7TTT4A7Zs7_v!lz2-K2+xl7h6~3YHK;f^6(1AMoVh!6Pf->15c=G zs@}W1C*Hz~^E`crt{%>k8#T3qr$MUlah`M8|M{{{X<1C1eNQ&R9Od>lR+$am@c%40 zO%P0z+*M=M5C}-Qt~}4KO~Wn~?Ts-BNkd0b*vxtXc+}5O$f$Z6srLtk9}Dxo&}H7r zWgiU1dHcUhE48PMYn)r`Njh#z80`xLJK|7no{~nC78-cJ0AK^E-ofhXQRijeb=Sye zy}Na5ydI!#OOc>9Ru!2|NvdV7SdtBw{hCcSzbT&|oFV>>{Qb2EmI3qtq7R4tlqViv`ir4yVhVwv=5b;NeharZDf7B z17%9&5@<2eaMNfF6B>RtTH`LU;AnC*%_?RyuOlERn7`mUHcekvpmNy32@=g>FcATS zPWA5l4Q7kS8exV9ipk|JSh%jWALpDFs^X@PHWHOoIJ{VOoPnC14 zztTh9wp*k_Q$#Omkg-ggqNY{h88&*HDy3^idz$NKZ_=MauR`B{lzbvp(cB`PYqdF( zC@nE%T8i~P6%lon^p|$EOwgGlo2n>vHO&ZaTUqOb{v7VB=iwM(uekr2OvC7inS9tf zDqV$;@f+}<@~^-+yWH_g-s(zab<-zlFH7gtd4ef(T%P%$yu3cnEY?<|qzJ@Ry^LYH zaJqhWGplx%Ei5@f)q07p$Rg3iJdt1XUk%krP*?dM@*i&pK(Ok-3a|SJ+Eu2%6YD>> zH>_1`HanU1&U-KG|GfzmRy00G)Ur;bwI(e#mN&{T+1lt@)ugFvT2!pJFQ&~qN}UPF zwKY^2U3gi!aDcY;R({|$s8{N|;aV$OH0sQ@>hUTw>FV8j&i<@jY}E6XNndbGI?!8N zH-7*w7&9gfU)HVCU};tB|C05ZC#>$KVyG7E*E_n}Z94ngPJQv6m*>?ASFQ8AQcfcq zLR--BijPIe*YIP12-ZD>ng524-7HvO1%4ZNx@T?6*>jeB-ROes>8mS&fK`BTApW|v zu4YI1Df4+43h|-?1RwgicWMQ8oQG5iKo)IdWcVrD!pZm0a{cd{Yj2R0_b=;qV3&M` zy&1sCH(kO0ccnNJgUIX=VRl%B&-Erk?Yt4Ubixc!8y8#o1~*b@ zmwz|SW`NB+q^x+csk|3AL8tErGF}z?A6a0=rxCRKFpY1WY!j^}eg*hr1B|ifd7h2T z1@~^D`*+2{lfoYgnen0mdG~Uu2qFXW0P6_Bbp56JilP&hLVQ%Y;^QHYIGd2?fXe4G7o@N!1~Rk)aPwA+631XN(+m6eA&u{-dJ$`1q*m$jg%E9D>v9mx zz$w=E8Nf_rAs!|NL@i73nqP&=oA9-*LbkS9Uj8;3sn?Yj*zG~p)aaI5l$Wf1*G*M* z+FL5EJvV}|Vu4#|LJ>;tMVD+w?W}~crd$T1d%U8}%ChPpn8l%FW7Ry)A%r^t2@iqp z5j0@(Bp1#Gd*b!HR13#sFoGhi@H)TyjyYn$W}s?(HvJ)-ha-ep1x5*1^g00ZC_%9Q ze($JZB3+_n&e%S=P8qTE=>!>E1onrtZ$%8`8B35Mq1$p2u;wew{yIpU`eA@RpcyN# zC?0D`Hj@X94v#sgW_L$D+=b#wS)2OFR^s?ki zJxhJln7?cgzSfE9r5@aF_KJq~o40poIdqJcoDBrNqOw=4tcjrzh_ac-EQVCAV6l&Q zgkW6b16G0P3ILaCB{X`%56rkYVAte1o3JNg5OGAPA~pv`MB;Kx$kPsOPy|GF^IU&% zplBT|eyF^y{*>60_u_HLU*m{qn+B;@LRcz;UO{Sl;#9g^PDmcD=e)n%U!UBe&7qw& z_@mWJ;)w!8VrFRKDDVflLy{@oulvFbs1e})7R3hV@!Vw+PAx9OuLWgCY*z@dmPEgS zQH1Iceai~gk8;B-N!1FZv5!h*SWHdfCLu8hqR77EHki=>HIO(Qn10zCkMl?zv1o=egR z=BWI^&W}X@oPjOEE$>?09v>}a*OVKUYrM{Ay_DL=OL)j~g z>2*oDM0G35EEH?3B5sLd#!W;fl6qJP4-7T0mTjVD?{;$zUG%?j3M-LpGmZTCK9}2T0{JBZa z#umumJW41U%-J&@F!;xkcqn&W%Wrf_U3Dm8e!B8Xaw+AM``a_84Dif$=6PEWehO2> z(AhaT&A&LU{vNSv6reoU%~Y~>CO0$1&98e>;Y&07$N$Vm8HvhB@{6(3v(oV*JJ^X& zD1VUgp1>_&lbf6kdvWikuj>e#PokHK7do)6pssP$9f_-oKPKw1xa*S37ZtpDTXK3S zj-Pm$|7pS1)6f7d`H*(9?q{vZe}ZfC&pSOIsZ>fB>=ev*8k*0dn{pLgvB9*b=GdMQ zy|cUhlsu~|+ORd>CT6cN^W3AiGO^eXxTnn|?i9(VFODAh=%4%e#hWw!I&iw8m`UV# zIM_IyxxlHiEemuzL1$0m-pbDc43*U+)w!JB(~`JLtG%r~r-l~OE)kv(EwB~VsZnHP zP7CS523t#D6CdmhanH5=tzzxd;2!)6Lx_&BBV2r)*8~EnZ+P+0PtCcM&4!wPIe7u= zup9ss+pg;+6dzHtb*BP3n}6*YuY$sWxW=dSbHY&^N|)P`=(4OtWpZx6DAATvJn)}$IS>CR49pMr zkR~QI=Tj#IO^lrm++I7#`MnKI6tYyk6>(=W%U=@`wo`$YEuBnPKRid5N5(gQdM;;6A57ujQ%IKSTXBkz zP5dyIzOZH)a+J|_3a@kw8&4O0&7AmiW?U#ZbR_I@`?u8eA|iTHipwH+Rt0*}&UmA7 zI@sn}AuC<*vnk5TtlUedL(1xlI3}yd9?qLNiI$RCOLi`$ymZ{R@3-cImT(8Yt9dR}D^;!pA+x zZfS`sp21(z3VLM4(`vsqAX)%P^vJ$5JBRUS4eZ$E_fkJ^29Y+-P5C`NuaN>NW*jWCHZs(pZ9vBOs$4?ol7i}xLbt{=06V{`JoM5mc zK^TzvaB(2MiUqnt-F|`UC}v<}aDHKs@Yw+%cwv5FG-Q5o9E#KMk2HYTGxm|ox2zR8 z$!oxtCvfZS$diceB}>3$(&O!EoB|XK1g{tx2}<>`6zBH+&_W!H&yC2ZO>yNc?KVIk ze-X$HPcnkf={Y`r`XoRIn!O69f60D1$lW^_Y3Kzy{E8*yywsn%q_E|A`m_Q9rpw&& z`fAoByFNoT1$j&jO>^W_9+R`^FCm+tdg$_e`Xn*A2IIuAs5t=AzUy0Px^ zM-3E-&&MdXFF(HL*U;Nk;3tQs)_c0P?^yR9?N>Gn!`6yJ8=3ukG$vXwUx*516!k8M zY{67h1>|t;9Bf1I2-32U!%> zeVaiz#Y4(MipK+zUJ}JQp8T3Uqc3aX8p;C#Is{Z8z$PS?pvQy&9)0_lK|u5`Z9jrTCF%p?pqj)dQNfe;eB##Dd<(AmcFh^f zvte^-D5fEj#B)AKnhcro8Hv1AKF|Dhw{*Le@%^!5H$fA?6CjkJj zK$Jc+4B#D)?Fy3DzKi%=^uQZ_%pO#)Y=ZRSCem{By9&b7&glY|Jmk%vsP~%SVNwh; zF(&$O?ST1VyJwYzd05vW^xT9+5{{IJO5xS0Z_a{`1x~Pwq>sTlA+lktxy&#Tj<{~Oz!LS&=*Sti(15{M)4M`UyX&ndgY&oB}plMJUMHmRN*`7GSE z&NaRg1jV6PQ0%&e@CM{0yk2smcBqrkp=X0%nH>3MKpfSQe>{BQYBlCY-B9p7t zy$ae^oOd6g0{@|}(=1j=;C8@g`8*U$p%fFAM0$W4ZsYY2OYI{f+IK65gleJ)`8m2m zPoYajw$&tW{NpdD-iqHEwxQWl9+9{Aeb0nA=IYl3AC<#}7{;}H;e82h4`S;KRCh(V zdSH1)yy`(^4IJ;mB=YTO9yHiL5F7RdJj1qlGH2QJkQWPHQZ~4Dh8^kN!f<=St`Vy1 zXY^iYklcdDlwFqxd3T%ayX0d7G;ow1q?bX00#lSUny zgdmz~vK;})AFcoHDI5jW4fSGA4aLN@KOmI+IM@fhg)kFhikQZG#o`wgJc1drgPR=M z=wUoevRAOZx-s_x5sV|AsM;kyBt8J{Hr=^DE4*A$v{Bedb|!1blos45HPr_@Ry&qD zwmK#`M%^Qwbj-5P78Oj%G)ZqIX2oPBevUPCKQRBIPk~I{#sm{iilM(p2@6Ea5l&&| zW8|Z^ypiM+j~>{2fZ)T;BF*}pwUBkxK2QH(-T_s$a(3O?LA#m#WPXKn1ws69;z492 zcdGX0-SgDXBGH> zuVS`ZHq#HLA1pr}v__g*+N2G!ZMfD=;g-H7zuU8s*J0S9qG7+|EW9p|yZ!o|R6@XGx1ei|7@=dauo- z1=Uq&y|t>HMO%tlyigKah=&v}^iM4*f1~LAlzk6`Jt8wn&0rkhiw%p{rpnbSNik%eJ|+a}9=Grl zQiNb2&elBS-V#9ERn<{nI~u3SU56!N$!#bc2m2Fj51jW$XKzRwLLZBl8{nPy@AxP`{Ly|;`-nRdUcvxS+( z*W&5o?~t35iaM<;Y?jVW@V#J^Js{~|xLCxJ1Pjp%FI;bF2&`RuvP^VFuIXH_W`a-R zF{UhOohGNgXKit_|IWOvG#768o#NOUn-vJHy3zX*qbn&_O~oRJ9#kKX-%*)G(HYO*q$<8mXfD3TTaHumTSTO z-lVQ|Ww--IPqZe_Ki#j#KZ=07AJf>#o;Um0G9aC!7R8qJF3r_*BAqIMSzLM=a!`EKEY zTtTlLs>z^*AC3DbZKnR3Ua#?*c~5_WdxAadKg(HQBl#r#B z7^|JY5D!9pSj0jo^0~NBZC^>_SVHaa!Fzi4Rl#q4)sbw0cBk-Z_05&2AA0Y2H}IM` z=y;!{d0*(+8usbpR!zSHF50amE?pCz9lgEVNEF>bCO62Qw1$24^yJt8kQuhCf`hj$ znz60k&|ES0T8dbNH6r$#@eRZ7uHhL(!aa%Gk&3oW>eth!Rlne{r(HJuu%1cUeHM)f zR`YIfBxHq{w^3HF`;O;`d~ForHYJEZa0bnn@_^N7QGMThAkDO^ZRm)`qHd#lfw`H_ zcSQbE++)Ovesu$mS1O0;y%yMy)AZk2);*Ft&Jho~;!`G_&0V(C9>{{ZSDz>V&ji~5 zYBW<{)qBjv+^HI*RTA0!W6Uv9Ki%=X;{m=Xo;%oF66str^Lsy_U--4ceIRGtH~r_B zkWYal$E7B)T}W4SW7TKIjeG^Es}Hs|g@KfJS{VS_5l(eqzBdiMB3CaOOwU0OFT5KXPl19BV1?e%J3jM<^rp5Z zLqWdDt$N_#4%l4%d&_kC_!NTtK_Cblledopa3hXE9TWmMuQg|`GhJeS?_%UGQCtGDjy8|vZGrYc2R-|v=BsAoOOs3NSNM;_Pk-I) zu9@(A;Ju6dDCM;GjG!sJ)j>i=m~tK^#k0ObZuHI1u2}CN?=WE`VMz+J(ku5E=olV|3as^_jdlNX2g zrFYKvrgxY3r+4J{Xb-k8^e;B=tgpbY#4qUAWs?{9cbj*yPvHC2dzU``&)ESAWanO) zYbfqu)O)1+uPeVLeNuq^*7V2w#j#;kpm9Q9S_#MG}3M`Xcdz3#0D9&1u>;w~8W4YW_H`@W>xvFEp!gJa_I!=>Ed>L|VF ztQC=CV8xEj9`PRW5$CnSH1n|;AjnIqE7!Zof3eh*@f#=Z{-iemJi` z!=hIKf4Ys%o6UW37#^168&9RW@5ym)v0g2%S$7$FePp_9cux7bV{bN@-ozD*UFvlj z9DtV*X@5-jSu6_FT7Tq4^LOrqQUd{iJ+>)IuP zs`gf!FN>-!yX^^on-0(8%)Tokx2t28|H%i620kC}DF}R=@0Uz3+dxXyE3kDpxh=5EE`8-uxO#9S7y$%UNngvy*@}@BJ?AM@M3u;Amtu_c85hwGC zOCiH%a2!r&$7boiy#8THMzm0(3{g-S_pqtrBWbNcB@fnu0R zc;%g`YatB*A&}-3=QWXj+Wz6@w$F5Q_~)H)jeby*dfdhyecitn5;1Rx6yyM!;Iza? z_-i1Yx`?aUTo9uJYdV9qMG~{sw))~L_g&^7 z?|RbXmETRSGO5_vi?}`XPIla^?@F&3`Kn+u>(czR#QC$)veY-5hL-_eRq$EbI-ufu zRuIzPxdi9R*_vJ0>dVlr1$QpBtSkn!@vDk!n3qGv!|2rQlnO7JCp9Ao$Jf7R%n}bK zucx<%B1r7rC0!6u5HV}7GL{W=NQcEj?)=xrPqEzgm|tWycaj&ffmIZVIiEHe2utW` zAVn}K?FsFopHz+c0w5Rp39|>GE!BVwEQz5+hx>ceEKCq2f!I@qH%Kl<#Yr=IhIIz0 zc6Rpk?~j&0-`!FyS$awHK3`>}S|oco^8!~j@|f*0U*6KShJnIYQpOD*9*r$*9z?6j z0%kcIqGz6`m!SU;x$hC`zeq_VN)7Rf4n}pAQid5cD9G_rbgF0SUVavb`3GfzXG(as zvbAYQGzvIfbLU~Ix+A5&Lg~|R@aLPp$y?BfNQ?*kL>cow&O*7ka<8bm%HCNJKG7J^FlKp-dalWQs&rM$_h5j8Jq~TUMcRkWtm=GHCX86` zYL}=`tf}4PDyy9DRjx4of_1eErmcT7KHBOW#v$g+9BNansp*f*5_&JWg}E|yVMCCH zZ{AxToMII%GhSlSB29i68MZ!KD{Cz{F1j3Mi{TqQlQJ3IV7on7kKxz)nz zSFo1_VzCxq%h_q8EF!jy|ty%T`#U$El`E3(fau^JtmFJ!cxV>{|p!I;kUmf?c){Es`rn)@( zEgv5dTa`0A6+uI3+eN44PNcP^zi&J>o`fiNbPCdzrRsk@^A^!8dW2Ks%62OK2{Lkd zl92Z*EynGx=*#wkcIUMQ9Kksu<_-*aG-aVfPCRN{I@PmClT1Pmlxno`=<9ygDW75~ zGs?8|tT)8?G5R+P7zo6aK?oj#S5Mx4YTK|}4e3xGF6p-+-gR{KE#FUxCF)rliLF|LMI>1`1)n+xGY(JVBgszG<^ms#=VUSK3px$G!*=&l)t z>LTd-=!Ll?VBCef?MV4#ZWwl?tBa_kE<(*h=&^t`I1-q9nPI4E16&R)aYZ;I$_T8L zILf|b!3+T?y+Q#z5U`W+K}!WYt+UriX4*UyyV^vp7_uqwHS2Q8KJX6MI_2oz@o`*oA)ua?coH>-6tZ2Vx8jjiFD{L=%eWZDWc-S_PoSHNNk5irMSG8SkP|FIfadjG}J9v8z~GKajT>TYetpb+D5C*f=nyWUYm94AxZL35T>5M6sG|>8ralMS zESVU*!ipZxUpzeC;-ByD2QSe_HWORP%xfY;+ZqyQ!qV01)7Lon_7bU4w?EK+?jy5! z{*tvpnTtu$6p4yWt0QN(iJmVfEYaXsQs!5h^Ug0c&!=+8FARn!Se21==%1%Agf&vngTinhfw>-z-IJO{n##O%$fC;n z*?RT(bA+XhUK1a6B20Zx=8|FR;j-RTh9r#=pu1xSg(KbQ69FM&Rx46FQyWv>#vqc! zB$Grr;=$T9PQf|sbj#JES(z|)pv28wWFM2BMrI=XtFg&4t~OIG=9b}Lpf;*D!Ac~r zpiopk2l*#XOK1I6Lx<#6(8pf(m3q$3ZbK(kt|&Uy%>x-?Q0KIZB(s)Cll~T!hLNqv z-)b9wgYA%0tAKSq{{|?49=b2-f*eI}8dg7>jU6^hT%*2*2SZiK-=#k`dn2{~*!5d# z=2gvUW?ZUDpE;#;7!Y-NEPEy~wWWmnL_Di1td|oM=VJQcVgu4TL%0vMFLMLyvuh2o zF9vJVPIX2Bn(Evs4fJR9=1h^`plkHbzQO-?xloq5P?+$KayOQ_1jlmsw0F7GlI_+2 z06O|cH@*u)K$s!Gnb5&=iDfC=urfMwC)bcKb8vXlM}lB4HuGf;XUUso@XKb%vG*`Q zx97_?MrS(MqrSp}=x+SXBM}|t6fbF=Yh+PTP!+}b7xxRzVLkNeU{relEem?uutX;; zCgMRS++QuAoA;$n%24auGhJg(`zYzc+F>BopP!Gg4<+}JCB-XV3n}y1t?Smm-!2*p zqFy-TBmc%1)Vk|D^md(i`8w$hTt0xcEy17bss&b}T~XTe*A@@}7P9GS4%bSgLlLyG zuS!n-%}g+$wzLczwvl^rBJXo6=9rWZ(#ZICx6Y2uj;UiyXV8KaM`8CJe~dYF0n)c1Q6Dd~UtgBA^| zYaKCuZHE@^!=M#Kz-Wtg)_`gF%cqnj(a0`+L?Y|RXeaj-0? z`>%C9A7dQmOXB$vve;m6e13{oe;3S^Fx$;7N+)kEMJ-b;(e5@_^7NxP1urXM6&{?^ z4{Yl`%=tb;+kpA$EJr<~*k_%02un{~qVJ-0oEh%EZ_O81J8Y=fll&0Y0qHxc>P(-L z-9y*3859I6v!sqRPhkBb3O5#yi6M1EDDlDDKV#$hX8i+(JB;8vVvkmlvOSM_B5>L@ z`#+`CgSj?fw01xU{GaS5^-zikYRFH5<6`%nYkk%gh$!$!ux`m-e5H@635GUCi1)o= zyfJ=@6NU19ujrtuVUAACBZeEn6@MtNCr%B}YV5+R)gB{4##117kBRmiU zBztT4XUZx9ynZ04dBU$Fx^I){x@pkrSmK@mdc6UsXMETCK-G@7*>Jt7JxXD(?%B(O z&>LP*!4D~Qp3J*xVU?%D%5sg|$|iuV7jvg%hjM4BmpHI1teEVL>}VQwqL+QK`=NPo z?dE00`fG2PINdfw6B>8URfStQtuEGvE(qu-lN0ddrZ8WqtiXyQeueB8TD8ebsU z(UovfU5sEKV}a+flw~?p*|5cPgu6c8hDerZD@HEgrVv!zgEq(dFJn@U);^3^64w*i zQ!y_hxGxeHR5odA_*tr0d5EmJX*=7DMUsVRRPYHB7ZRX+Fr^Voek?~V@n!*ij*slN zu2n-5rSj~mY@?l8AIEGj@h)=C*@j<=tk7g)UfT!d?LANX*sp6_(ET*w7{$j1OGfi< z3n2w>@}?QHAF1U#o#gmtxClP5>z0~g-!C6bwrdHg*(jD9E!a39p-rN=c9XoZ$%bgT z0f3xPeAe$XsU8aKGl`m$WnG&}X+L9(i=1mmu3Ev4&MsWz*2MfaLLtuae39JeS(a_O zEh1CRr6O__^h3kilnRqF5GAQ2qhp2P_P}`SVDp0Q@&S%*1vQpHHXL?bgYbb><*~cr zU8th%-r)5gO3w4g&xjlSLfYS(OB7dvGlbv&3Ppg z5Z`e^{|nbRB@XXIzwUe6g_4wuB#B<1+=rVBef?aIKsZU z+tG~hfkq|YY{6F=iCkig^ayd0@pYa5bfYO4eu^a~BNofD;0?Ru_qXsCT7~zDNpcD4 z9sdF)=v(#`!x;X^e?YV4{F)MCg*F3#3V9*>u(HFIF4CCZem-SqqC;-fe}ONfLH0R- z6w-m*A%V~jN;Q6hJj#LG^ns#PpG!m8LL2y?oqD2>+@SghfeOu5fzU#O`cZ-IDgWt`u~7VzQDUbQ{W}9A&`s|8|3IL z$W95D>2@@H!3+NQ1txPv@nEnnKB9rPd30Aa>U(bl?s{1bd^ll;zn!dx<wA?>-gdleV~f!q(Wtm=cdg__)JPIkRTe#P_1PHg%2 zr!l!4lv0)&Rv-S9X)()+7d~}jMI%4SHDvw>asSAP{4G@A-Oi)?a!*oxjhb7aO=&`) zW?AlEd+g-UkzK%iHPBB)_~N~J!m`K2Nl(OBkRx^_ehOa;Sa4_4iM!-Taf>OjjXmwd zmQN9BkNFikO2p}jZRh)4hfK@CsJcBmR3E1*QP36w{ia}X*(!1-y|ZRlOL#-MepQk8 zHD6KA{eqqo;m3f3uc2=iKCUb#>%b((P{!npOUDe~563``Z3v6pcN>toMso`%Q1TG% zq}rloird0eP4p{H8(!n(R)LLB{242*JrDCjpGvSJu*ez9zxGI`$~51|u4fvx^H0@^ z@kB`Wmv6KDrRummphm)VFKcHu2;=O4A{gnjA961mdC;b_u{ zry%1o;vf!qAVc!p#=Toij50HNXh59WL7dvKMt)tGDasJJGYy7xLtf~+MQ}-x8kqYq zj3Vh*`xmJgq8j)n^=P;H22Mq9Jog15|q zs$rav=Cr?GS@dQ~W|o#R2@YI6d6{b&y^fofvme67C-jclI6)t#>bDBbRXnB+N+f z)NWa?r?6|$6ZpgVi&KwVnNwMC?#CR>8TtL)z5F?TJEweH9wQpS74R&h9PXF6TIOgk zR;XhO*!{RExk(kUGFS8px3<~UO4UxBUh~egt*Yj-XsG_ZOglWcFCWei6QiGFFBKC5 z;g*Cl^b)scACMoA;6q9KKNx$*C`+PlTd=AUm9}l$wryMIq;1=_ZC2V@Y1_8#tYqhR z`@SCU_3a=1Cw6R&wMWDlF;}d;*PH~dq^1P1@PzbgJ+Qv1-!_;|sC} zR|-q}>rTg$Wl4~w41d@mMXowz zI|rC|nmP>0%v+7itix5-XEqR2#um%>LY#-aQ!YvVgU+&KPHtP(BHWVpOB~j$+H?TT zm9G3bm3Mauf%U$rBJGySzY@J~u7HRhx9tAwr+Dm(AKQ9%cDioHBw)RR{z4?kXHvr7 z$J!d^qZsC1@jHow80>~z3S{DOv!Nl+Ht=k%8#X*JME&{~;9yv+3(`g0Cn#tnmYWu> za^5UFTn)B78?328qQfdZjfHgg^nOfaw~uUOlJW!%l^!#pH#4dC@c06Efy7nvO@h5_KU%yakZeuET-ylP`}Ilux_#i@hPAAOKYr!EF zCf)b95eLdYjo?-BS3yP$gY0rK(U?$9Tm{RpIjdHaTJd4=Pu~Ro)!e<~VzIzcfnq%0 z3MR%t2s3@~TgyPq0ZTi8fQ*omva&H2VzmU|U)|p53E4S9oQ@*Z-8}|ox(SKS;EWuc z%xVTB0qJfC*!~q1dP9mciRW02mZwqeKyhH7xUrC;E{BIy?oyR#cT8ET|Hc2kM( zbcVlqmm*$8JYKp)td|i4r&tF-;#iuqi;51&KvLEz=h{7;x^sXsS2FT>sijUiE^Myl zS^mH!g^WTL$_iv5Fps zVDD2*j0;MuR)HO|5}*cC zZczBo@cFumx-$@wRI2>Oe93`i!?khc9$8wPIQdM`l2)KvRpwWIUvVGtugQhvGw_qv zt+R@ObCssEbEI5F-o#e*cJ1mNZ#8{^>;q^LG!iN3yf>(mEnFn1JLdGd0}Dq9IYBWw zIWsY#Zlx=^xnk__aSP$aG`+Ypxf%PMmpuVNvpfSMzIJmwUmWyp5wcLw#8C<5kIp_; zMq=VkQ3wvHgkM^1+H!|aInV%|k~xtU6<0LTa+M^~q{4}YR2A%b%gv5RHL>lk$go8* zQyQ&V+>?UGv7|g&tb5Wm zXTycbzf{u?2Hf_finf8%RqN~?yYX+oX%afs8lq>8S$xd797zkccKIWE_I z`+0$Mp;IDQ{g!vo;59&A07L05Jy0BN;$RrAR{M6q9MXbarvgH>s%e2ny4)Gn@p* z4pT$zoZ{~aXeWy%wbZ=2j=}bZA6atjBrAnnilmfbry`mDi-{9p9u2E`@^x(>F2a9p z3Rwu2!BL>va1N#T1;m|P3{TCqL+OH)W)Uf9&CpJ3QFeTlqJtpi7XZ(V(ov;}AOw%I z$sz_ER+(f*N)Wk-(I0^{htgWKOJ#WjB!NHQB8g=5yCU4ALv0Sib$7yXARf_qNClDa zZo=NfR~;KTHS5G(axZpc&Mr9nvKlk*Qx9!rHj}(r?D{_P?YQiSK92qH@OeAtbPwA& zt^Gai*QA~kuO`iHp+kA}f3w!`#&6F$E=6d-4b^SLEN;+g&@%lt){u3CE*Fel$jmts ze$+##BTlep!PQB}BuvgnLAEv*3)x87gDxszs_Jl^caM`}4`T6y-~`d=7$8f)G0I(of3Ug1yU%&P+ak+Am^%~ZN+{>9E#&ev%r)2TYFpC;SWYi7dE|3- zu1rXTiO`tUhk}O&hfS0?KV5!mYoQgOV0|-}pQu51D1!3FLvsh5c|~?mI{aajAF+w> z+P+#F4vKUCc-^4miCZ6pOe#PJTEkxe2_OSI(~u=XY8<(7u5Lo4v6343UT*^)`_EH@ zHLQK#o_2y0=fE^HM!U0q1 zr&W%4&UadXaz{VMMn^Iw>&p8^<*WCpuU8mGryS>ejuNoJop+`;hqk7F%oS_06UD!D zZ__Clzf4X6yT>c@_VjhfmsAY9)0j^qvb&;2-#^+{%PuN=5N`}qOBZp`1XO&?%ve1x zh*F=GDWxL+DxKFat;M-PWBZ^Q1(xj}<6yu{tbj9QtMgZ~m^uPSuIRUpQS6*HQMK%} zc02}Jmn$^u1|Dm-Mn`h1Tdme5xG}ml{r2eflTt?H9XI03Po?wZoZK5)4|(P&^88LajPYMAdoWS&*>g&!mA&%l+jjcoOr) zlQR2rN)ZcZCi5QI5IP*As0#ou2J@oFoiQN;Q?M#b zq%os?OWOL@6s?ZH1#>3NM1uVyJ-(;ge0r?s276w&&jp)cegoD1P&NLG+rG>Yq%GR! z{M+IA+qP5x1Mj|#yUAsgJH5H{v#Q)hjmTTc0+$m0cS62SQX2VV`%lrp+H@8xmie|* zi8sS{b_{y*!~tt8;=*L9RP(vhD6-lib3^As-g&DQ?Fqe7=^?~q1cl{_jFHytpRqzW zc@t64#SU_f?o7c{y@jF^%H_n1?4Sz)t0;3+QcWe8CG%ucCi0lhz5oXXqAXL;Q}Q8+ zL6=QU>@|`%&GpqZ_*;AwbR!L1Vlm(d3fQQUDXCtvm+sxm!EqNhvOZadil;6;DQ+B2 z%J=bu(P^pf)N@)dw$bq6<1>#sg#WXQA(!Ep5`G{;BL?FeKmc2Gaigtakc$&^8 z<+k>Wb9rz60$^Tj*Z6oaJ})0ZcJF_)GwmKp4_L>w;D5c}4>2 z>a_uH4>>657TomCeCCfYD)ypx8L&3>eO=qKn=vej$a~P`>`wYa1WRaJo%jdU4BKJXg}{ z9F5Q-jp%(cf#1U(6uj|}p;M@oVR||@S8EX}1N2-9gb`0(my3s1h0|=}$yCP@NxIsr z!SGMCqDQWS0lBC4roqU5`x1gS$w0lbX&4FB@ygl6F{3(4(zys)e7o4aNsZXfsswkF zF1b9jTls}tQ3>)3-!z(>mfY0X+<%zl3#pF$>22MA8$SsP@@3{sFIbFRYu!hx1m(^|?fhr1O0J#YU& zzzg&m{!HIxek*_JS)XeAssmn3sQ~b3ogTw`hIpBLT0OPdV~`EAbbUlRUPttpn60Qp9LpiRt(4(X^UH=Mu{(gC1dJuoOVJ{ zMf~PQP-<~9n)R8SjQTNYq%yzJt+^yR%AzHML72OZXzS1cR6ZDnQW>8v%7=XF7wv`wo+uXY?$ zY!ALGDq|ntmCoIMoT>Tk^P5t-Kc4x1$8H2)2|j5-`bup(y-RSjYzxtg*NvyFacp+i zUAy$2>W+yZ(}Z2|lk`Va!a)KV5)>zdgN;!=`g;>)4(N|SgK3EtNmPeoba%!g8b1~y zj+lt5Hm;&kme2|2$>E94)g1oRkJESH!;C!4K%}#Nq>VhIBlr4L#8Umap3$XgW`uot zuNw(413TcB(x%U6^K6Ry3rEzcY*t6{sd|r(s6sOyaq==>lnn6^xDJzo=M!E`Z{7A) z65M8%DQV;7Hn(j<6e%KFY2Hz>XRqeoMX{hAu`2zO|cQ8RR zLRzEXQfyulo^$7Q^7ZY0Z11(jQDkQ@@p|hVwQjg)tSgxv(#e)jd$X9DUL7UDwsq** zt4uZaYA6a)kRY}7N%x{oG(&P#`ZY#5R*yYFjOG1S12efJq9RpF$Ra=xEMK5xqrgJR zQezZg)cT`puR^HEamUQ_*p1o^+YOH2Gwr(II^?=F{C449ZrsjLSw_nM2k0O!qsx$< zW=hst>+V_qU?QCjk9-#3d3}x<2_8~$ms37?7E8s+s7oQ6HqwmXN@Xq4W}p1&aqk$3C zOpB>kkO&E$TjtUN9Ybcuq|c_-6~rsT9+_|Sw<;Zro&%V0lW?2+G0^sXh$h=+<6E5A7__^ce`XFBRZJF07^ z=V#{T=OXSe7jC6gRu)8^?(pvF{#k zfM-BN=Gm9`FO^M+(=z0fgkX+htSXdA@52}RACa8HRoG;5UrWx-{lDGO; z&2s;BJarsD)z;<8MJnsg4Xun@(TtSJ9oc=CH0QaZ03IhoSrS+HBA^!opm}H{czF_+ zqZH%VwunVYX!t|kt;3RiRdOrzDRfvI;0cKG?#{6=tciXG`wrILP~bHsif0?pM( z!JG0RE_U>*hKltGoNs>br*XB*Qje;gkVC{|jmkYzGJmkV19JF6O=G)cJ;|c1l|?A` zYqOF%O?SXc*an)OMW>5=zeWP zg`27x*$cRMXiYo(`A3~1B5r3KI#p9uQ9;vg=C&?3RQ|9#6E_BFjURECeAthqG$c-) znuUL`V?NT`9~THc+NjWiJcsPyA>bh(mz%&Yg?+d|quCV|lNg;E8+*JoXqz;ry}3RL zH^#ZCynzBCUk+``Xdygl!_2C_I%$@;dHM9Cqy9-f`9oDGyyH?bCQv;Z_eB;3Vl z&Qa_)=?cW2^!&;C3dUs}Uo`a`>ga+t5V z_jbFc#exCGX!|5jGn1_PX(AV@x~;Fwo@W;%@j#!RQ_*hu+H`N`++4yKlBTa5zo#|V zad|Ag)OfUX2WH))GX#dBW<;VErIYuIx{XT(Sa*Y#xY&nF6skggA~||ok4$x{vTDMc zEkJ&gc$k>1%rgIg9EDu%&=5NBfs!RfsO%}=WeKE5z2~S*3uQTa2Y;GQuFr#m)k4_TmFS=k%8_)`zN9(IubU(&VQ=pwSOY<8 zbS)VP#^qAZ8g1vkL&YH0w_a|2sFCKgpk2>2E5KILlxcraLY9888XqWgPOgwi8%a(Z z(I^1muaRJ=6f-4``DLeR$aq+Whm(pWE)9s`Kw#G8sZ=<-k&-3Nw@0qxN1l9BWi;b2 z;N3gNWJUH**5X5aiNO5UBOl7ww^_L%1*v!Tec_qCHpbV^_PzW=FUQ~hIbq(69r_0% zQGq|i?lgbSs<_z`Nyz7wfGKIoVhFh?+^ND0u8s9E1ZEL??qf{eG?$7``jzF)@|3f9 zW2)G`Wh}+PvW+%tVGm_+E+^ffg&Vmur7T$ybL+}TX{zNKsu9wz|g5L3Y4!mXWm^nR?^V^%|*}g4@S~^N7?~@ zPuvNP19@dbT52L)cgze#kRmVl-M?lM{Z#dUh8351&ZCA1Y)4;8Mct?4SDSRt_1_Ks z*0o-354ws4W=?A*o)7)3ma+KyZ#VWf7i);b$2eVv0FFBcjj;jAjU;-bJ(QfzyOTet z)Umjx+&B^)vJ%|*CJB}knU}zqN_n$xy$qz+m@!{Vy-jq-%nTi&&|zu!xk6JEKPs7eaFx3hRk_p3*Cx^` zz{Hi>t4H#vm`?WRc;$)%M6)F^d_7FV2Wsk|AEgRZzjKh(9i+rvDoVGy`nq~IcaCD) zr(35xI9qrty^Sjz8&_*F7pW@LNvJ$mNxFI7mCOF&vzi0?xC(MxOvHSw9x~??5U>+v zd&!Ed#!Qolu?lo*1;*Cl`3XXom5eJ9| zkhaiZJo6HT)VDLL_eeV4ZaY~#wnoSOLgPW2&xgYm`7MFoq@6aVh!UKl6vKv*hKYy7 zdx64HOj!7Nov&s6{GMxnvg0(;fH*&{&!iz;cIzcCHyR^ugP)OeHTMLzVozpo+c$ZH zWdNdtBb#YfA5^fsUL_QKS_kEt)`4q3S!>lDGnsR)mxRQ^8^n87*8iTbd^%^zrKfrx zK&!u8FnUhju6Y5%Hya&mlfxWa`R4mYgfLnd8xJU*MFB8!tm}TW;5fVYe5!M^C-Hr# zm~L}likW83rUI^;ZSd2HUzg`K4eZ=-rQ5!xfN{8b4F~>C9+z$5Od(Sm%xIr(x#+(E z4_MH}G%E;tg07g8xyH+OvC8~MQMH3;H*+Ywmfgyh(T8Gb@RPGH9s_T!wpzNhqS5bs zu1ys%i_s**3sTpC&1uqmp0u|$Q|pDQu*`7uSvnlWK)qdD(MTwCRBIO2jHGAuOQ(6k zJA(-NINUSMtm8WKeG@Zh*OBqjyIV}KH4fHNP&r%rv(@>+=OpS(iBCAWk#D;3gZwyV zg`Hc0cq^dm^p9oWG7S2$gNiBqEoyNZFQFMUE$~r@m03w5qN`qKND@RU)kTTno zc-s%vwDDJP!@vTpsZ+)8JK1msAqN+7*3iEL%DY2`)r=MYJ%F66>$lXQ8YVkUm*M^c9i!mfQk{p z`_Itk&@ZdZ7qL9Mn?BQz$cp-_O)W;V2T$8BR(+c#hIsas>h31yoGk{R-Anab4QKb6 zC|#OgReRrI-AZI4RLAf8$i!@|!b#WlMdjS=9o9ik9eRSV4CW2Z;(MxBfEko~VnpdA z;QjT-tBUA5NE~>mP&n28OHc##t+153YhwNwV63$k-!* zoIPm5At!aTO9Y^l_IQz7l1AdDV=N3_9Ee_YEnZQjMtZZ50cgLj)^yP3v7-Det z6iL_hx31wMD69^>%G(ERo>a4Xv0H7*kYU8IVJsUO5rdf4{ikHtn>I{;2YVx5X2^yp%WFDZlNPmE`0i_TLNov~h>xXk=sG?le3kSO@ z0Y40y>VQJ=1@q8%g-k@o+K!{Cwv(vnk=vro)nh@VuehJS)(>m0yD(yURJYjfUDz;6 z$|%E%{>($5SEvkyd73kAj3V6;U8)HBNaW5!jS|5&sTG&kVb_~?Pu2OBqPd5Yk{_{F zXo#N;7^oZV-JKqE&f!3PHn*`YAY(i9V4d#3X9T9vy0}x_?PD(N@&ooe5Aj!C{ zfIDVB01t!7SsU>!_ViTGfV7J*B1PtCo*zA$LUP`*Mu6`(NgyB@Mi0Zhq#!;|c64?o+(((G3Id)lY{z!g1}T#FYJ|YZ^ecU^3|zQjwta zO#4hXgL|ZD>|x$P-f7aI_FeU(>zdKO&5Bqb07B{c%gkSsCz3xUjp^Z_NWlXNM6Nl> zY$SNn2WDhTCVTG==m87WuKCBd7LbmN$`81?^Tb&1(igHyETqMB41C5sAKjk{6m*#+w}f*B z)B2=Rf~r#TS&}w+W9#jcq6-PL`C{4P0m4|}3IMVrcA0l7<@g|LdDFuND^J5`oAl8m=j*YZ>W5qqfVqe>Ysp|K62NfCV;x z5tyboH8nMueY9OpM;AQkt_iNB)3l7cZ;~ztZ|ua|>mV)HC>c8dS&p!xQ=9CcW!Dy2 zP6tuQqPFWy&dx6PGgIUZI|lWczu3|h0PC7_l0xK4%ImQY@l#4%{vXd+NplWd^o?uP zuGnSy3j8wi`>Q$O>)PTa*k*2KFK1)h1hywz%$>*X%g;7b=kB$smWKv`ta@+`Vfi|? z6wjxfeA(pm?4fRV4qjY=as-@YLJoIhu;76^qs;C+?u}t6=8+1D-+PRrIYjv*@w!RM zNg4;#QkSYKdP(|8>MAHy^z^heG>`uhn3xizEJ@sDNsL1kJ|)Uyyc1F7!$dA`H5M!& zHNplH1n_31%m^$ws2uBjJFeqN0y(DcC6uDs2$`E{2_);(DU#t#D;gzUAHSn*K4IIV zhrE_O2A0j|42F8OUE7-2N>ykdy4=4!p?f{1xiYNV8>-@22ONH{I8D0gq)4Edfr|1k zAh-iMcuN;%P&m6cGp6on*JVd(dRjMkjQ^=y)k59VW9>&u8_@w|)*Gyeb8CLoey45> zwj=13EAN?dq3abo=37?YyXcVWl?)P>>=t4VwZ+#8K?PQ*Tq|KpIo`VD^J&ab(=H+L zW(+0VF{S1w)fIJSD(g#WC~eW|g_6pQ$+}om8itFf6C@p&GmbK@?n^sOBu*LDFdv0z z=xw2J*69L_I_7>0VbQ!qFy7V$z5kou77F#0b)eA|Vi#%`Sp-`__`oU(e1kkCf>XlZ z6u5#(YsAp~8cXw)bW?YW1(x;kci?Cl`)y!uB>@LM9Vcv}$!|#vd61JGtJve;Yu9R6 zTcgjpwg=yuNbpEr5Ws3vsz{WH{-(-E${8sX;5Al>i}XpEbYzg$5f}vi=+r+t^VAA9#Je&Z-u^>9M7zRTexFF$Q_XAmm^m6uW)@L4dJ@{fZu%#Tr z@ZTb8t;WN4zaz85R>%Ih8ZF;wIo<^+8%*UXJEUY^bJTL`PIZUxE|+P|bF-`Gx^cbW zC2`(Q{v0P6epy<~b>HL85?c(O?6SBb&wfek=ykDaA%CHqE(MlZAx^w~U1jK1LvsEZ zR4en%r(dFX5#KIkKM7Lqlgzm3_SE;SqxL!G|4ehR^(&FGDeE=-vo0H*%zg*+ z!!A{IEqHBxx5zKKysnSy=iVd86{sUv6yw5&w#9kGPGyLa52s;;RC`W_@aw;R>NhFv zGez#Q;OeGskz>n4{k(5OSNGu`-oviAYwU~&(Qq&pjoz-@! zb~ns#_9G?+)R~~_5yO3nonCpn`!+;7sK!c$#jeOc zo9e6T+rhqsd%uSFrTUTk#wKper(j~|StWge*lqaZ6Fc|!X?UnhpY#5xcUr1q8k@b9 zF>c>6j1{0X9Qsdx-+s_s8f#r7EQCH&UFJvAO@KeIRpliv4m|8d!(e9#4nOtF zJ~&)m)wUINd$+MPR%;+H9*~dWS!c%{1bPC$)xrSVF%l1CeN|$|8ibpMM-B~v8}`Bo zcaIFDV>QdI$Dfym=yT?;-V)+_vA?$vU?yoF03TF?&GDD4&rEyPN9(=7pId|HodZb3 zagnD@@O~VW?H9O<4$%?TEwv*1aEsCQxR*8@W*izE`hCUw-<$ql zl>xrUE}#0t)F2QvkKen`5Wi>r!`MBpsIyX$I~clU^OFoLZ-N<5PKyhYapU+ z?j)@Etjzz|*ZS#^!$FdVlIM*x12dcaRahh<%p5-18~=+JEb(5&j}2OTYa3%7#JA9D zKp;^hI|L1EB`1h2GA~fBS}vdyiUvzN#Mvxc%~P_H3TGNQbs(m<8T`R?t(K(`Csfa+ zU(0A+Pzuj!c1Hp%g+`(y*Fy_b60AIp?1ufkxJZJ3hCz7N$ z6mVtxYik@yL2;Vrq$CmU1Qqfro?I3ss<W_4UF?E8852G&P&lWcsCHa3ajKdov35Mfg&LN=swL-G z0g^`Ke0&D44RvQv?XId!NlxOy^9_jxSiDNU)@gMsle~7f!SJ}7qfO*f18L^#UoTYb zr5fHWp~T=p<&Jp&a4%-d$0kcEa(r7;7x>sVHN9~gAp@{3g3w#u8FBNc|;s9zE){$GnW?D zrW!`03i<0nhqO?W$o5!npM0PwKrI*#BKf5g;~XAiJ=t|ttwujWHTb{f##YqF4AJ25rSR4)0takG`oERp3vCOC@ zSZgJg(AHWHHe&#-KW(JM-lSBfnZ*`$O0YB<7X0Oryp{3r6FJmUqk$~LX_U6!IBhMh zU|fqoy!-8~#AM9xou6FinokcOd5cE-ImQ;9vFtq=0-xAOit;|?L7anjF&){nic5EB zD0@9%o^B*T66>Ovvigfx5Ca`iVq#{IMLBYekn0R8f3MJSEbj-*F=}xEo6(P8A#c7v zO1&{ph`A+Wu4#AZ-Vq*n`BeGJxHAS%1#|8me-yow#gZ8h7bwJ?3fb5!|C~N*HpBCT z7&m#dDKZzOCnHk}PTqW<70Cd)_qY81d9OKbR7h_L&%vxDdELn9GMB_!arjOXucG|S zGgvKKy(P`GipU*s-UJ>v!FEf7s%1t1@_Qez+U zRrMsNXa%(mAIq45t&HrG-btk|$>st{+C&_G=}|h*4Q2(ho@*=XXM3dtB+ZzNmkBD&w0ru$9yN};!4bYCaBSwN953>7CR8)rkbJX{=+B%g-Se<3Z_z` znzKS&i@scC=K57R+RCC!Awhm0$=OAetwLSVTxBMs==q#lp{2K2WNcx5U0uzkTnz5V|)m{XYmn^>ERZU@WX4Icm zm7SFMSCFK}s@rQ_uC*YjLh5tz@_x~~`Z0gsFdQp63@;Tsam8+w6eRALrA`Xv} ziSAus%a>X!(S9m9Fk`Aooh-NkRWWmd$D^XPkmO(^suVFp+FGEcSR9OMUaT^hayrLE zsZ4A37n|aDl6FNNf+*F&yanP)vZ$OuitDj)o$!&p64GMa)Tu)KvLFs70+gR0ye zp+qvhv!vv)FCuACN?Nu{YDV2$ku(L%j&0raFRzwQenlWqp+QBZ8r-@vP0GI(s|Lsd zUL`Rum0v-FB5)}sx>#MkuT-I-K#j_jQ}rV=O5=nSs6|cy6;XWIsrQEE*YH5(v`<|R0w1glikt}vqRumV4_ zccrJKMlyphq^c)4%*wm4hWO8zZJkAqvPME)eG$qzkq(t`Jt71wBcGj7-J#fm5Tv{< zOWiUF$3G>U}K#wBWMO4?|jt21U{iJ<+zO?;& zSMkC6wyj}8>Ey!n{FQ7{N$XusJ13Dte1zeMV~^wDg|+1Ar=3qd5oIqo0lAIRZ3b{U zWPp9oxUC5bh|O(+4%mEB4UJy4*3??g3fQUh-82Qjc~T`8T_P{C>$>!wa(tQ1MX%5L z-n8OQoN{=-K0wCm+WjQlv1dR>l0)VIsRbPN>tkvSs_&uWZ}BqJa=vB4k%;sKw*v79{xxNrzX}e5%LY zptw4_)@>oSRPbUjKPN?~2mJ(%6=YNY4){_=Epor@hMw-*&S6uouW>c5-rHB)>Lqf3 ze=SLuu+_dQ%ijS1h0{gPFUJ~Y%jeIYAMpWcx^8qd@RJ+X;_acNDTWO1N@qH_CVD(g zPc{~pLqpuxGsLBs(ETIRz0IkZx%VqzK7YGI(pdF`M9a8vekoiWSHHtye+i5$if@cv zJ0X3MQuoXs?chT$BDUOh=nQKM?Vqme;q$++o@J_STi1E}FWiCZ_iw3Ad9A}YTyTCq^Xb_!d~bc7jCYwuSm5I5IOo(Uub)3UjX@S%YeGmYwNpqnD)Ow z!9Ck>+tS``_y6=!h`iI@UOK&AkKw~1`$~Ip>tyxplkKT}N`U^8ln~C%qZSwrWdAnd z>Wui^Eld|56KX?bb9jA)2D-uY?ldn)499zA^b^gdP&|CAx4`-#J1DTKHrmI}F6@#0 zgpKI{rvG;uSNgbS&*WMRDxxq<*VL=*64|vprce9YaC1}BKW!M(-#O&h8$eq}%tar{q33?LxF{pn*;_L{lN_W4z5?}0_@HB1;HScr-dj|y=Yf`nbS#%?HFV1kLfZBJ+ zFvCP&imRn$k>e9`P5SJ@bMxlx#FN_U1+qU);CG*~*?HPu{RXdNMz5-94dJ_Zm}^_M z`H_=nwRnQSeqRrj;W>1?=O=xTv{9zmf4~LS-2gBshN#>-WUPwtU;NtBR?7Bx+;pFq z5^6?JQi-4YMVTn!5SnBgW>G+^B$f_lYKC2aLzyZfUbKg4-XN|8T>#SF@k;Tjvk7lM z(3s=`RXqE1F-hJeNa~n#_9y>&l21Y>!MJ6@^=a7~MI2FeEnJzIl`77uHQd>rH^$E? zTu=NC8=8%p5NutQ!DDpwz(S_~@gBaPE(3wBkp&bF4*|W3yPYWky}XfyvXk}y!x+uT z%E(DTFJ^A#Wa{u;S{XW-ikKSPntYFx`+htB>2h#%B4A?qKk2v!H8dTO#Swg_Yfr@Q z^ETT6Y@&*$`xrWq1HptO_NRiwMD!9`qFRQL`nk4d#k@{GN<`Wi){NWV&OJTU?2Jf2 zCFO(?mPkFybmZLekw>>0$|?gt?F+ zvYKd8^Dt~^K4{R;O5&U!!ek^E@)ClGaAsfxKYhSR%4sQgsJ~PEgF^J6q_`-G2{LvC z1sm_32n^x85KLi+CvUz}s`fH8#kJy6Cf@HJCAxZQBJsi{B;+zmy*M)>8UBA2~Gm za8cBIX?1h8(MGK9EpyNX~VtE6%w=nc)pf{;e-StnqmAgO4(hkH@LU%ave3I&%C6hH|g-$Bp=C@@)Nk$hn<`R*D2;$T$$1H z;m^OI`w>XGCiQi4dd?=#IFV~St`|4v#H-2Asg8RuBs=d4SY3y%h20G?W=!KDEs4>y z)cGu}iE6+qps<@66%!z$YISqjam49ZWWk>u`6sqn($0flb-kA#iR1;i`uvM~Jiu7o4=qP!lR;%FHuQ-b^kBt?bC9#N;%MwYnc- zB|%o}?K;h==5JKMI$WFBD%mCrot>)K$gZawDen=}w5AHR4oHWRYgGG0_NFCold zNe}(IGhaZmEmx;sFxRVHa=k23;O9&g02`83)Ii1NyLOj9jvtOk_ zG}bUdkvok>vfr47dm9g=ZYa-X#eLplSu;pI@PUA7xm;sdZ&LJ*=A5F_4EHRGkigSH zG|gR)K~N%|As_N~Vss&JM<=js*V!Cs8*iab>4(^!cxvIP~c%Sup8=s%+5dYJ@rA1%wrCsIX z>1jQZeb={@v8Ba#<8uNd(YLMZyH#8!1JM?ywjlL9D%Ze>cs|5avUBs{uR(a4+7imHm%-1yP~tIat& z%}w&`0Stt5#SaIvLQoG@={$x)PzN?(X^<+@rj((ZTICmRa#jHXHDF|q$1VD@$d*+O$_bvpylnQTNIX8dMNaX}$9 zvF9E`n123zARjK7SqUTOHe%)OQEnG?^dGXC#=^=ff*~f+-FF6||I6qF(ic(OdUzsH zib|zH!t@~Mc|*Xhh#)`fQSL;Tw&S4>RFE#StZyXR2w3w!ANnbrOQ){9*6?yiY|2S( zZEpO`>mQDYb-C_FGoOT8jg zI=L)Bzq%?w&yCLzDR~_`U&&n^bhLp8+x#t&{xt5k6v4YRc0Le^FXTB!(Q?@B#bDbt z1`>_Oun;O_t8ugQe|2|OL2-TA9>!gRCb%_DXr!T$4j!BU4M78q6Wjw01P>4_XwU#b zgL~ud?iLydA$X8rL5IwLW^Ua(SKjZbI&Y^=)qYsDSAE~w`=>KmFjmDRJm_ixeO&;0NyIw?S+;m5!%5a%^n95FA2TkIoGE1E0^h~FP{l!-k%;y+ymzZDK1F?Q6)ER5 z@+9vB#Xu+AI2Xo3f&Zwp)VnqVj*Vu6LEnelV%HQHe33b@<@IELT)6JMnfg*rQmt3> zvOaDbGIm3_V?nZ#$}9DG6iaOF-A@#mR8pNuINnAc9_IlHvc^gh1K;!jbyb8>3hSzX zWoNoPWheM!>^gTq?BqJ_+S0%h*o*Guk;-^QUQL!D$RS&e8+vb2N3nk&+!Vb(n!cHo zO$f%o2{$hK3EP6c%)OB1|GnQWzu>_(M-xa**%y7l(PIVWcsS$qkW%i$(u$#yZ*}1v zl;4(twK*0q$|*yZ6+KD&j*Up@bDQLwFU+n>38p{KpOiE32kYh`X-wu38#L84>*TIL$bKk6L^Z z%aJx^dlANx&g^Uny@1kMHdDj#h`t@}ZsU)Q`o76$jYh@&)(I9Z1%T(20Qam(Vo9P? zZq}x$#pdx+AxxGC6HSIKOwJGJZR@2QEjXe&r#PHGwZGXpoYjyv7PrdIjd2EvGaUdM zx(>K-&y;N+5BLQxl0Rpezgk$~)OfmEya;t{h6ZDwO!}qZK>IzWlJkWP-3cL@a6vjP zuR;``1c|S_iN-0GfsK#K!sK6x znLiM8g%o_;RNX<(^kBT+ORZTOByk+zdF=Cz7IcEivU@EfuIv0sX=byUhf4pBFM*j! zLE(2r>WPzpYg+AFng+#*xXvJB_wR-qttH4Ie_Kt5@iJ~?d=G0_y-N7ZfYiL&X|tHG z+pN2WVjJ;mh0v>FSm5g6W=YnF5*+$@J_L1^(nWxL8)<1)#=>CQ1N{tyK?dNfZ3&xbzt6 zD4GRVd$Qa&4{1eLqILs&CYLMkz#;@rXJYP*@qH7G`7mPY zgi<=?L|;e$Q_nJ0H9!Xd@Cix^MfZ+nWXrt6`5L1nkesa2T(d)z=?ey%{QFza-(&|2`>bA4s0y>?+9tNE8X>cZMMNf4xe= z%k{OUms!|>H6|KWg!W`0DzHeidJMQiZSA{D2ZW~Pa_?N85}oCyFlw7a`4{n>bwTH* zH?$C75sl~w{Oi%eN6R)*k#m3)BG*-YQ+laY6Z5Lfz4}exH*Qr*E==O~8Vr>|tl^a+ zWnJmEB~HihpxRcg?M(W07wI687^XI6We!JujoI3h zZr~^^wCDwMgN&2&B}&W&_bxhRb5MjF|6BSP^dGt(1bl%HQU_yXn4VY06GxSBdBsoc3jPft1Xd~Z zP^xLM=jlO9yqJU27|#2sxs)6}eCq7kYX`}GNR2RAA%p!9o@T#QrxyM2;7QRGa&$A; z*x{NJ9M2g0?zGi9Dpmb`qr^=aHJ(K-dq$@UzYih9M*x=7+Q3oubiafTFE+TPnzz1& zN&Z0UIJFQmc7%;*B+o9%@k##ai4rg4!ij#GwHSgizKU(H688*ccCH_m+U5)s&9SPA z#G-Kv*QM}mJdptJ7!8@eHtcqqDuy2|DdOWBP3&JDOYz39Au`Cjk<9WO9~8QJ>p^)p z4;4ukQNu-s8C?_f_d?~M;H&?8p|Wu5Di#5&@_Sbux^UgJ_64-KjO-Qvl=gH9hove( zx=X2sP|or7F&DoVTlB9W@PrOdWi+Ac=h@dHKEy4C^4jUki^yr4O|3AC%#nj+N&_M< zK!_k+*IeQdbsT-^4yd(CBO31SCJ28u&$1yGBs|gC_zvzX4Rh+gLM@7KmjU?QH3sD2U(KWa zfG4>=+A=KhilHA}sb$WLGoX|q-R}B9ReL_d_S6DG`hg&wzJ5EflF1Fcp~MQTv;fX$ zZ?z&m8Po!fWN2+(w-z_=c7y{HjMNnTUG1Trxs?9fC7PKx9gz%r$F8Ld#+Bxrx5Pzn zP@y44ot%OD{!MyXA(QwaMw|IWOza>Z=7CMtJE=GZoD+MD#dvtv(2bSM&%uj89zo7~ z`VW@*SPKVx8=e#SGq%CUcaee;-p&#{V3)1POLQN3h-)##M`*y`FZ9O? za-*FKDMo@@_$LnM$G?_qe;7l2kuMDEj(=qq**_vkg}29Mv~A7DnBH{g&&I;6#B)V_ zZQ3Go>hTB|8Cta`i>D2OEDO^F#9r7Q`aa91lN*zGNUJ`tC8qBznpQ2g>chw==3#*N zZQe#gyNTkyqFlMToaqxE>h%Adv9D~^2;m#-Y&znigwa408jCAIt|G<<8;uYL#dK0H+M5o zf{Jn7-E$`S7@TQdvD1$kVqGd#?V$xHi3F$1{MP05Y`l|LEX@1Z>{79*=IR|QlW8-S zD8$>KSq|pxt8jcmqvUOPVN!OeWAI5a=lv5hOf~W|4Y#QUOCnL=4| zwvEK>uOM!!dyl9O^#Ldq$#^i1FjSMnZbZ`O!1xd|Tv65a&edd0PdwlPG;(Gnj40w@h;teqa%L^#Xsr5zv#$6 zSjGP*I`SX~{!$A6g^v7L>Oa^2ztIsO81$dBDi1Q2W0pZAVTZTmO?)Jw)>=}Uj7ZvY zL!Jq2n{*URt?Zp^^nBI|wWTYwn3vqx(w>2|igs3_v_$vdfhO(W7fEApdph0U`XX(Y$?oJu!NJd18BU6?_1>O(~|%j9{e` zEAyr1^vjbLj@F}_k~p!uN$_1LP-C4?h;CU4Y@q)d){@EX%}p_gwD($!Og zOhW?8DQJ>6?}uBl&ZiWcTyg!#Ss`;;62ZfruW@8GxQh>IS}9$%!Dy1YF--WrVg&u{ zV>gCpaxMhNyOquaD)ZVJDEYhD*i`Cd=sZE66o-9WNiA}9KL483lG#4ruPzdb@uy~$ zcSu-`(XxCw@7_%q08+|Te*D$+sZ#*@0_o@Af@KHux+!KHwCp0@rEr)&lww3@xx~JC z2D7}(GuKvBQcHZ&H7tWeASY8w++4Y6NwN|psYb@$MOq&_Ko{>?rOA|67T zv{Tdm@i?g&zby@@F3OdaD-^c&U5;OmRJ#iybubu6c!Ys%Zua9_cF5=2$GtYwTl|s= zIwOocObjfL*Kgd~2b8!g9&$`5ib`mKfhb)z<29p{mNtZDa3_L!_x&=h(}C@bCmgIo+$xu9o-u6 z5k~?H?;y5i!oFlLX0cY_nVoO^UN>Cmf-#!X8+^XCemQJh|3D#$J1=;?#*dg_Qs1k1 z9+53vL9;?1b`}D}p?P#^^S(sIx{iIE@vP({rJr_M8p9J`Vl2kE#qLCAA2XRV1dggT z=@%!dXj+dXZzxPu+6$MxeU0y!m=Jq2ra1k}823t+p|%K`KJYM1Nq@u*!k%l7=Q^BW zpBhSTO{3PK^WOaKZ0gWteihK*TUkZ?y0W-wuc1d&dpu2-wd=g=#&STzzafEpD1tL5I@3B>L8u9FyS>D% zT1%K-u*?)Pi>qsw?NmnX?Q|7By;u6j|#R#;eKEv-Cr$dY+=c->-I-j1D~ z<7ce)wOQfZ^$d*&g@+TQc36l$l0~lzPXX6*36bMe#$+$}C9;270NsVpUh-5eaiAjN z7gVIf9wt)}x1+ie&XCW`023#n>W_+0-|B1e_T&s@W3gAZwm()T_z~&Iku_+-9Y2rK z`&&?5ak50r;qVB$bURmL;h@B7tMhsp>!cp5Ib~3bhBhGOJmB(wgxKMFXVF`Bh_bcrf+wa-+4wR z1kkm@S`1FCc3zs3)xqfoUQ^ZlHX^Br&+S5f&iU^Xlg zFh+0KrD}<3j`|JAmyj)CG;8MHY3Vqh`k*9DZv5J zZsquzhfnl^JY}v7XvuW-MqoVsQB2`|t|2Ty_Gi)huQzHYM~Xr~@kguAErXTf2MYAL zeh+~sYNtH--IzTN&Z;r#^B%oD=NrJnN}Zp&{^;(IycILBaLF1rO4(9R0)lt!LZQ(E z6a<|e{2d|O0LqD(7V)K5Js2r;#QCT^W|~Fq{`tEuq$s;;XFn6c=bw&$FqHCd2jea+ zR2ZF0yy?x`Gk&F5L(Jc)VY(R@Nv%f_mm)$(NnPn{Ze#Wy^UG}QFxQ6^r>V>l7wXs9 zP5Bs0&__I18zc9Wh0b+B;^G?{u%1K5PT$<45>T2L?8(&Yukz1?NPKk9RWqb}?Nkj? zUn@iFd>IIpS#NSFUJ3SPpBNFbeL GET some-bidder-domain.com/usersync-url?redirectUri=www.prebid-domain.com%2Fsetuid%3Fbidder%3Dsomebidder%26uid%3D%24UID - -This example endpoint would URL-decode the `redirectUri` param to get `www.prebid-domain.com/setuid?bidder=somebidder&uid=$UID`. -It would then replace the `$UID` macro with the user's ID from their cookie. Supposing this user's ID was "132", -it would then return a redirect to `www.prebid-domain.com/setuid?bidder=somebidder&uid=132`. - -Prebid Server would then save this ID mapping of `somebidder: 132` under the cookie at `prebid-domain.com`. - -When the client then calls `www.prebid-domain.com/openrtb2/auction`, the ID for `somebidder` will be available in the Cookie. -Prebid Server will then stick this into `request.user.buyeruid` in the OpenRTB request it sends to `somebidder`'s Bidder. diff --git a/docs/developers/currency-converter.md b/docs/developers/currency-converter.md deleted file mode 100644 index 64f770608bd..00000000000 --- a/docs/developers/currency-converter.md +++ /dev/null @@ -1,56 +0,0 @@ -**For the time being, currency conversion is not enabled, feature is still under dev (check #280).** - -# Currency Converter Mechanics - -Prebid server supports currency conversions when receiving bids. - -## Default currency - -The default currency is `USD`. It means that any bids coming without an explicit currency will be interpreted as being `USD`. - -## Setup - -By default, the currency converter uses https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json for currency conversion. This data is updated every 24 hours on prebid.org side. -By default, currency conversions are updated from the endpoint every 30 minutes in prebid server. - -Default configuration: -``` -v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") -v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // 30 minutes -``` - -This configuration can be changed: -- currency_converter.fetch_url can be any URL exposing currency using the following JSON schema: - ``` - { - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.77208 - }, - "GBP":{ - "USD":1.2952 - } - } - } - ``` -- currency_converter.fetch_interval_seconds can be anything from 0 to max int. - **The currency conversion mechanism can be disable by setting it to 0, in this case, there will be no currency conversions at all and all bidders will need to provide bids as `USD`** - - ## Examples - - Here are couple examples showing the logic behind the currency converter: - -| Bidder bid price | Currency | Rate to USD | Rate converter is active | Converted bid price (USD) | Valid bid | -| :--------------- | :------------ |:--------------| :------------------------| :-------------------------|:----------| -| 1 | USD | 1 | YES | 1 | YES | -| 1 | N/A | 1 | YES | 1 | YES | -| 1 | USD | 1 | NO | 1 | YES | -| 1 | EUR | 1.13 | YES | 1.13 | YES | -| 1 | EUR | N/A | YES | N/A | NO | -| 1 | EUR | 1.13 | NO | N/A | NO | - -## Debug - -A dedicated endpoint will allow you to see what's happening within the currency converter. -See [currency rates endpoint](../endpoints/currency_rates.md) for more details. diff --git a/docs/developers/default-request.md b/docs/developers/default-request.md deleted file mode 100644 index f071d91bad6..00000000000 --- a/docs/developers/default-request.md +++ /dev/null @@ -1,44 +0,0 @@ -# Server Based Global Default Request - -This allows a default stored request to be defined that allows the server to set up some defaults for all incoming requests. A request specified stored request will override these defaults, and of course any options specified directly in the stored request override both. The default stored request is only read on server startup, it is meant as an installation static default rather than a dynamic tuning option. - -A common use case is to "hard code" aliases into the server. This saves having to specify them on all incoming requests, and/or on all stored requests. To help support automation and alias discovery we can flag that any aliases found in the file be added to the bidder info endpoints. - -## Config Options - -Three config options are exposed to support this feature. -``` -default_request: - type: "file" - file: - name : /path/to/aliases.json - alias_info : false -``` - -The `filename` option is the path/filename of a JSON file containing the default stored request JSON as documented in the [openrtb2 docs](../endpoints/openrtb2/auction.md) and [stored request docs](stored-request.md) -``` -{ - "tmax": "", - "regs": { - "ext": { - "gdpr": 1 - } - }, - "ext": { - "prebid": { - "aliases": { - "districtm": "appnexus" - } - } - } -} -``` -This will be JSON merged into the incoming requests at the top level. These will be used as fallbacks which can be overridden by both Stored Requests _and_ the incoming HTTP request payload. - -The `info` option determines if the aliased bidders will be exposed on the `/info` endpoints. If true the alias name will be added to the list returned by -`/info/bidders` and the info JSON for the core bidder will be copied into `/info/bidder/{biddername}` with the addition of the field -`"alias_of": "{coreBidder}"` to indicate that it is an aliases, and of which core bidder. Turning the info support on may be useful for hosts -that want to support automation around the `/info` endpoints that will include the predefined aliases. This config option may be deprecated in a future -version to promote a consistency in the endpoint functionality, depending on the perceived need for the option. - - diff --git a/docs/developers/features.md b/docs/developers/features.md new file mode 100644 index 00000000000..b9bb9053ed5 --- /dev/null +++ b/docs/developers/features.md @@ -0,0 +1,12 @@ +# Features + +Prebid Server documentation has been moved to the prebid.org website: + +- [Adding a new bidder](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html) +- [Adding a new analytics module](https://docs.prebid.org/prebid-server/developers/pbs-build-an-analytics-adapter.html) +- [Currency](https://docs.prebid.org/prebid-server/features/pbs-currency.html) +- [Prebid Server and GDPR](https://docs.google.com/document/d/1g0zAYc_EfqyilKD8N2qQ47uz0hdahY-t8vfb-vxZL5w/edit#heading=h.8zebax5ncz0t) +- [Prebid and TCF2](https://docs.google.com/document/d/1fBRaodKifv1pYsWY3ia-9K96VHUjd8kKvxZlOsozm8E/edit#heading=h.hlpacpauqwkx) +- [Prebid Server User ID Sync](https://docs.prebid.org/prebid-server/developers/pbs-cookie-sync.html) +- [Cookie Sync](https://docs.prebid.org/prebid-server/developers/pbs-cookie-sync.html) +- [Default Request](https://docs.prebid.org/prebid-server/features/pbs-default-request.html) diff --git a/docs/developers/gdpr.md b/docs/developers/gdpr.md deleted file mode 100644 index 8da2e917623..00000000000 --- a/docs/developers/gdpr.md +++ /dev/null @@ -1,31 +0,0 @@ -# GDPR Mechanics - -Within the framework of [GDPR](https://www.gdpreu.org/), Prebid Server behaves like a [data processor](https://www.gdpreu.org/the-regulation/key-concepts/data-controllers-and-processors/). -[Cookie syncs](./cookie-syncs.md) save the user ID for each Bidder in the cookie, and each Bidder's ID is sent back to that Bidder during the [auction](../endpoints/openrtb2/auction.md). -Prebid Server does not use this ID for any other reason. - -## IDs during Auction - -The [`/openrtb2/auction`](../endpoints/openrtb2/auction.md#gdpr) endpoint accepts `user.regs.gdpr` and `user.ext.consent` fields, -[as recommended by the IAB](https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf). - -## IDs during Cookie Syncs - -The [`POST /cookie_sync`](../endpoints/cookieSync.md) endpoint accepts `gdpr` and `gdpr_consent` properties in the request body. - -If the Prebid Server host company does not have consent to read/write cookies, `/cookie_sync` will return an empty response with no syncs. -Otherwise, it will return a response limited to syncs for Bidders that have consent to read/write cookies. -This limitation is in place for performance reasons; it results in fewer syncs called on the page, and their -sync endpoints will almost certainly read from the cookie anyway. - -The [`/setuid`](../endpoints/setuid.md) endpoint accepts `gdpr` and `gdpr_consent` query params. This endpoint -will no-op if the Prebid Server host company does not have consent to read/write cookies. - -## Handling the params - -For all endpoints, `gdpr` should be `1` if GDPR is in effect, `0` if not, and omitted if the caller isn't sure. -`gdpr_consent` should be an [unpadded base64-URL](https://tools.ietf.org/html/rfc4648#page-7) encoded [Vendor Consent String](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-). - -`gdpr_consent` is required if `gdpr` is `1` and ignored if `gdpr` is `0`. If `gdpr` is omitted, the Prebid Server -host company can decide whether it behaves like a `1` or `0` through the [app configuration](./configuration.md). -Callers are encouraged to send the `gdpr_consent` param if `gdpr` is omitted. diff --git a/docs/developers/stored-requests.md b/docs/developers/stored-requests.md index 8b7177160c3..9adf4ed1309 100644 --- a/docs/developers/stored-requests.md +++ b/docs/developers/stored-requests.md @@ -1,6 +1,8 @@ # Stored Requests -This document gives a technical overview of the Stored Requests feature. +See https://docs.prebid.org/prebid-server/features/pbs-storedreqs.html + +This document gives a technical overview of the Stored Requests feature in PBS-Go. Docs outlining the motivation and uses will be added sometime in the future. diff --git a/docs/endpoints.md b/docs/endpoints.md new file mode 100644 index 00000000000..88116144a41 --- /dev/null +++ b/docs/endpoints.md @@ -0,0 +1 @@ +Endpoint documentation has been moved to prebid.org: [https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) diff --git a/docs/endpoints/bidders/params.md b/docs/endpoints/bidders/params.md deleted file mode 100644 index ebe0401c2a5..00000000000 --- a/docs/endpoints/bidders/params.md +++ /dev/null @@ -1,24 +0,0 @@ -## GET /bidders/params - -This endpoint gets information about all the custom bidders params that Prebid Server supports. - -### Returns - -A JSON object whose keys are bidder codes, and values are Draft 4 JSON schemas which describe that bidders' params. - -For example: - -``` -{ - "appnexus": { /* A json-schema describing AppNexus' bidder params */ }, - "rubicon": { /* A json-schema describing Rubicon's bidder params */ } - ... all other bidders will have similar keys & values here ... -} -``` - -The exact contents of the json-schema values can be found [here](../../../static/bidder-params). - -### See also - -- [JSON schema homepage](http://json-schema.org/specification-links.html#draft-4) -- [Understanding JSON schema](https://spacetelescope.github.io/understanding-json-schema/) diff --git a/docs/endpoints/cookieSync.md b/docs/endpoints/cookieSync.md deleted file mode 100644 index 2378aaa1cdc..00000000000 --- a/docs/endpoints/cookieSync.md +++ /dev/null @@ -1,55 +0,0 @@ -# Starting Cookie Syncs - -This endpoint is used during cookie syncs. For technical details, see the -[Cookie Sync developer docs](../developers/cookie-syncs.md). - -## POST /cookie_sync - -### Sample Request -This returns a set of URLs to enable cookie syncs across bidders. (See Prebid.js documentation?) The request -must supply a JSON object to define the list of bidders that may need to be synced. - -``` -{ - "bidders": ["appnexus", "rubicon"], - "gdpr": 1, - "gdpr_consent": "BONV8oqONXwgmADACHENAO7pqzAAppY", - "limit": 2 -} -``` - -`bidders` is optional. If present, it limits the endpoint to return syncs for bidders defined in the list. - -`gdpr` is optional. It should be 1 if GDPR is in effect, 0 if not, and omitted if the caller is unsure. - -`gdpr_consent` is required if `gdpr` is `1`, and optional otherwise. If present, it should be an [unpadded base64-URL](https://tools.ietf.org/html/rfc4648#page-7) encoded [Vendor Consent String](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-). - -If `gdpr` is omitted, callers are still encouraged to send `gdpr_consent` if they have it. -Depending on how the Prebid Server host company has configured their servers, they may or may not require it for cookie syncs. - -`limit` is optional. If present and greater than zero, it will limit the number of syncs returned to `limit`, dropping some syncs to -get the count down to limit if more would otherwise have been returned. This is to facilitate clients not overloading a user with syncs -the first time they are encountered. - -If the `bidders` field is an empty list, it will not supply any syncs. If the `bidders` field is omitted completely, it will attempt -to sync all bidders. - -### Sample Response - -This will return a JSON object that will allow the client to request cookie syncs with bidders that still need to be synced: - -``` -{ - "status": "ok", - "bidder_status": [ - { - "bidder": "appnexus", - "usersync": { - "url": "someurl.com", - "type": "redirect", - "supportCORS": false - } - } - ] -} -``` diff --git a/docs/endpoints/currency_rates.md b/docs/endpoints/currency_rates.md deleted file mode 100644 index 537713b147e..00000000000 --- a/docs/endpoints/currency_rates.md +++ /dev/null @@ -1,111 +0,0 @@ -## `GET /currency/rates` - -This endpoint exposes active currency rate converter information in the server. -Information are: -- `info.active`: true if currency converter is active -- `info.source`: URL from which rates are fetched -- `info.fetchingIntervalNs`: Fetching interval from source in nanoseconds -- `info.lastUpdated`: Datetime when the rates where updated -- `info.rates`: Internal rates values - -### Sample responses -#### Rate converter active -```json -{ - "active": true, - "info": { - "source": "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json", - "fetchingIntervalNs": 60000000000, - "lastUpdated": "2019-03-02T14:18:41.221063+01:00", - "rates": { - "GBP": { - "AUD": 1.8611576401, - "BGN": 2.2750325703, - "BRL": 5.0061650847, - "CAD": 1.7414619393, - "CHF": 1.3217708915, - "CNY": 8.8791178113, - "CZK": 29.8203982877, - "DKK": 8.6791596873, - "EUR": 1.163223525, - "GBP": 1, - "HKD": 10.3927042621, - "HRK": 8.645077238, - "HUF": 367.6484273218, - "IDR": 18689.5123766983, - "ILS": 4.8077191513, - "INR": 93.8663223525, - "ISK": 158.0820770519, - "JPY": 148.1365159129, - "KRW": 1491.3921459148, - "MXN": 25.5839382096, - "MYR": 5.394332775, - "NOK": 11.3144425833, - "NZD": 1.9374651033, - "PHP": 68.6139028476, - "PLN": 5.0130281035, - "RON": 5.5172855016, - "RUB": 87.2333891681, - "SEK": 12.2141959799, - "SGD": 1.7908989391, - "THB": 42.0074911595, - "TRY": 7.1224176438, - "USD": 1.3240973385, - "ZAR": 18.7774520752 - }, - "USD": { - "AUD": 1.4056048493, - "BGN": 1.7181762277, - "BRL": 3.7808134938, - "CAD": 1.3152068875, - "CHF": 0.9982429939, - "CNY": 6.705789335, - "CZK": 22.5213036985, - "DKK": 6.554774664, - "EUR": 0.8785030308, - "GBP": 0.7552314855, - "HKD": 7.8488974787, - "HRK": 6.5290345252, - "HUF": 277.6596679259, - "IDR": 14114.9081964333, - "ILS": 3.6309408767, - "INR": 70.8908020733, - "ISK": 119.3885618905, - "JPY": 111.8773609769, - "KRW": 1126.3463058948, - "MXN": 19.3217956602, - "MYR": 4.0739699552, - "NOK": 8.5450232803, - "NZD": 1.4632346482, - "PHP": 51.8193797769, - "PLN": 3.7859966617, - "RON": 4.1668277256, - "RUB": 65.8814020908, - "SEK": 9.2245453747, - "SGD": 1.3525432663, - "THB": 31.7253799526, - "TRY": 5.3790740578, - "USD": 1, - "ZAR": 14.1813230256 - } - } - } -} -``` - -#### Rate converter set with constant rates -```json -{ - "active": true, - "source": "", - "fetchingIntervalNs": 0, - "lastUpdated": "0001-01-01T00:00:00Z" -} -``` - -#### Rate converter not set -```json -{ - "active": false -} -``` \ No newline at end of file diff --git a/docs/endpoints/info/bidders.md b/docs/endpoints/info/bidders.md deleted file mode 100644 index 7c6ce960479..00000000000 --- a/docs/endpoints/info/bidders.md +++ /dev/null @@ -1,23 +0,0 @@ -# Prebid Server Bidder List - -## `GET /info/bidders` - -This endpoint returns a list of Bidders supported by Prebid Server. -These are the core values allowed to be used as `request.imp[i].ext.{bidder}` -keys in [Auction](../openrtb2/auction.md) requests. - -For detailed info about a specific Bidder, use [`/info/bidders/{bidderName}`](./bidders/bidderName.md) - -### Sample Response - -This endpoint returns JSON like: - -``` -[ - "appnexus", - "audienceNetwork", - "pubmatic", - "rubicon", - "other-bidders-here" -] -``` diff --git a/docs/endpoints/info/bidders/bidderName.md b/docs/endpoints/info/bidders/bidderName.md deleted file mode 100644 index cd525e640f6..00000000000 --- a/docs/endpoints/info/bidders/bidderName.md +++ /dev/null @@ -1,43 +0,0 @@ -# Prebid Server Bidders - -## `GET /info/bidders/{bidderName}` - -This endpoint returns some metadata about the Bidder whose name is `{bidderName}`. -Legal values for `{bidderName}` can be retrieved from the [/info/bidders](../bidders.md) endpoint. - -### Sample Response - -This endpoint returns JSON like: - -``` -{ - "maintainer": { - "email": "info@prebid.org" - }, - "capabilities": { - "app": { - "mediaTypes": [ - "banner", - "native" - ] - }, - "site": { - "mediaTypes": [ - "banner", - "video", - "native" - ] - } - } -} -``` - -The fields hold the following information: - -- `maintainer.email`: A contact email for the Bidder's maintainer. In general, Bidder bugs should be logged as [issues](https://github.com/prebid/prebid-server/issues)... but this contact email may be useful in case of emergency. -- `capabilities.app.mediaTypes`: A list of media types this Bidder supports from Mobile Apps. -- `capabilities.site.mediaTypes`: A list of media types this Bidder supports from Web pages. - -If `capabilities.app` or `capabilities.site` do not exist, then this Bidder does not support that platform. -OpenRTB Requests which define a `request.app` or `request.site` property will fail if a -`request.imp[i].ext.{bidderName}` exists for a Bidder which doesn't support them. diff --git a/docs/endpoints/openrtb2/amp.md b/docs/endpoints/openrtb2/amp.md deleted file mode 100644 index 16fa451ef36..00000000000 --- a/docs/endpoints/openrtb2/amp.md +++ /dev/null @@ -1,127 +0,0 @@ -# Prebid Server AMP Endpoint - -This document describes the behavior of the Prebid Server AMP endpoint in detail. -For a User's Guide, see the [AMP feature docs](http://prebid.org/dev-docs/show-prebid-ads-on-amp-pages.html). - -## `GET /openrtb2/amp?tag_id={ID}` - -The `tag_id` ID must reference a [Stored BidRequest](../../developers/stored-requests.md#stored-bidrequests). -For a thorough description of BidRequest JSON, see the [/openrtb2/auction](./auction.md) docs. - -To be compatible with AMP, this endpoint behaves slightly different from normal `/openrtb2/auction` requests. - -1. The Stored `request.imp` data must have exactly one element. -2. `request.imp[0].secure` will be always be set to `1`, because AMP requires all content to be `https`. -3. AMP query params will overwrite parts of your Stored Request. For details, see the Query Params section. - -### Request - -Valid Stored Requests for AMP pages must contain an `imp` array with exactly one element. It is not necessary to include a `tmax` field in the Stored Request, as Prebid Server will always use the smaller of the AMP default timeout (1000ms) and the value passed via the `timeoutMillis` field of the `amp-ad.rtc-config`. - -An example Stored Request is given below: - -``` -{ - "id": "some-request-id", - "site": { - "page": "prebid.org" - }, - "ext": { - "prebid": { - "targeting": { - "pricegranularity": { // This is equivalent to the deprecated "pricegranularity": "medium" - "precision": 2, - "ranges": [{ - "max": 20.00, - "increment": 0.10 - }] - } - } - } - }, - "imp": [ - { - "id": "some-impression-id", - "banner": {}, // The sizes are defined is set by your AMP tag query params - "ext": { - "appnexus": { - // Insert parameters here - }, - "rubicon": { - // Insert parameters here - } - } - } - ] -} -``` - -### Response - -A sample response payload looks like this: - -``` -{ - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_id": "420d7329-30e8-4c4e-8eaa-fe937172e4e0", - "hb_cache_id_appnexus": "420d7329-30e8-4c4e-8eaa-fe937172e4e0", - "hb_pb": "0.50", - "hb_pb_appnexus": "0.50", - "hb_size": "300x250", - "hb_size_appnexus": "300x250" - } - "errors": { - "openx":[ - { - "code": 1, - "message": "The request exceeded the timeout allocated" - } - ] - } -} -``` - -In [the typical AMP setup](http://prebid.org/dev-docs/show-prebid-ads-on-amp-pages.html), -these targeting params will be sent to DFP. - -Note that "errors" will only appear if there were any errors generated. They are identical to the "errors" field in the response.ext of the OpenRTB endpoint. - -### Query Parameters - -This endpoint supports the following query parameters: - -1. `h` - `amp-ad` `height` -2. `w` - `amp-ad` `width` -3. `oh` - `amp-ad` `data-override-height` -4. `ow` - `amp-ad` `data-override-width` -5. `ms` - `amp-ad` `data-multi-size` -6. `curl` - the canonical URL of the page -7. `timeout` - the publisher-specified timeout for the RTC callout - - A configuration option `amp_timeout_adjustment_ms` may be set to account for estimated latency so that Prebid Server can handle timeouts from adapters and respond to the AMP RTC request before it times out. -8. `debug` - When set to `1`, the response will contain extra info for debugging. - -For information on how these get from AMP into this endpoint, see [this pull request adding the query params to the Prebid callout](https://github.com/ampproject/amphtml/pull/14155) and [this issue adding support for network-level RTC macros](https://github.com/ampproject/amphtml/issues/12374). - -If present, these will override parts of your Stored Request. - -1. `ow`, `oh`, `w`, `h`, and/or `ms` will be used to set `request.imp[0].banner.format` if `request.imp[0].banner` is present. -2. `curl` will be used to set `request.site.page` -3. `timeout` will generally be used to set `request.tmax`. However, the Prebid Server host can [configure](../../developers/configuration.md) their deploy to reduce this timeout for technical reasons. -4. `debug` will be used to set `request.test`, causing the `response.debug` to have extra debugging info in it. - -### Resolving Sizes - -We strive to return ads with sizes which are valid for the `amp-ad` on your page. This logic intends to -track the logic used by `doubleclick` when resolving sizes used to fetch ads from their ad server. - -Specifically: - -1. If `ow` and `oh` exist, `request.imp[0].banner.format` will be a single element with `w: ow` and `h: oh` -2. If `ow` and `h` exist, `request.imp[0].banner.format` will be a single element with `w: ow` and `h: h` -3. If `oh` and `w` exist, `request.imp[0].banner.format` will be a single element with `w: w` and `h: oh` -4. If `ms` exists, `request.imp[0].banner.format` will contain an element for every size it uses. -5. If `w` and `h` exist, `request.imp[0].banner.format` will be a single element with `w: w` and `h: h` -6. If `w` _or_ `h` exist, it will be used to override _one_ of the dimensions inside each element of `request.imp[0].banner.format` -7. If none of these exist then the Stored Request values for `request.imp[0].banner.format` will be used without modification. diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md deleted file mode 100644 index b532923e793..00000000000 --- a/docs/endpoints/openrtb2/auction.md +++ /dev/null @@ -1,789 +0,0 @@ -# Prebid Server Auction Endpoint - -This document describes the behavior of the Prebid Server auction endpoint, including: - -- Request/response formats -- OpenRTB extensions -- Debugging and performance tips -- How user syncing works -- Departures from OpenRTB - -## `POST /openrtb2/auction` - -This endpoint runs an auction with the given OpenRTB 2.5 bid request. - -### Sample request - -This is a sample OpenRTB 2.5 bid request for a Xandr (formerly AppNexus) test placement. Please note, the Xandr Ad Server will only -respond with a bid if the "test" field is set to 1. - -``` -{ - "id": "some-request-id", - "test": 1, - "site": { - "page": "prebid.org" - }, - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - }], - "tmax": 500 -} -``` - -Additional examples can be found in [endpoints/openrtb2/sample-requests/valid-whole](../../../endpoints/openrtb2/sample-requests/valid-whole). - -### Sample Response - -This endpoint will respond with either: - -- An OpenRTB 2.5 bid response, or -- HTTP 400 if the request is malformed, or -- HTTP 503 if the account or app specified in the request is blacklisted - -This is the corresponding response to the above sample OpenRTB 2.5 bid request, with the `ext.debug` field removed and the `seatbid.bid.adm` field simplified. - -``` -{ - "id": "some-request-id", - "seatbid": [{ - "seat": "appnexus", - "bid": [{ - "id": "145556724130495288", - "impid": "some-impression-id", - "price": 0.01, - "adm": "", - "adid": "107987536", - "adomain": [ - "appnexus.com" - ], - "iurl": "https://nym1-ib.adnxs.com/cr?id=107987536", - "cid": "3532", - "crid": "107987536", - "w": 600, - "h": 500, - "ext": { - "prebid": { - "type": "banner", - "video": { - "duration": 0, - "primary_category": "" - } - }, - "bidder": { - "appnexus": { - "brand_id": 1, - "auction_id": 7311907164510136364, - "bidder_id": 2, - "bid_ad_type": 0 - } - } - } - }] - }], - "cur": "USD", - "ext": { - "responsetimemillis": { - "appnexus": 10 - }, - "tmaxrequest": 500 - } -} -``` - -### OpenRTB Extensions - -#### Conventions - -OpenRTB 2.5 permits exchanges to define their own extensions to any object from the spec. -These fall under the `ext` field of JSON objects. - -If `ext` is defined on an object, Prebid Server uses the following conventions: - -1. `ext` in "request objects" uses `ext.prebid` and/or `ext.{anyBidderCode}`. -2. `ext` on "response objects" uses `ext.prebid` and/or `ext.bidder`. -The only exception here is the top-level `BidResponse`, because it's bidder-independent. - -`ext.{anyBidderCode}` and `ext.bidder` extensions are defined by bidders. -`ext.prebid` extensions are defined by Prebid Server. - -Exceptions are made for extensions with "standard" recommendations: - -- `request.user.ext.digitrust` -- To support Digitrust -- `request.regs.ext.gdpr` and `request.user.ext.consent` -- To support GDPR -- `request.regs.us_privacy` -- To support CCPA -- `request.site.ext.amp` -- To identify AMP as the request source -- `request.app.ext.source` and `request.app.ext.version` -- To support identifying the displaymanager/SDK in mobile apps. If given, we expect these to be strings. - -#### Bid Adjustments - -Bidders [are encouraged](../../developers/add-new-bidder.md) to make Net bids. However, there's no way for Prebid to enforce this. -If you find that some bidders use Gross bids, publishers can adjust for it with `request.ext.prebid.bidadjustmentfactors`: - -``` -{ - "ext": { - "prebid": { - "bidadjustmentfactors": { - "appnexus": 0.8, - "rubicon": 0.7 - } - } - } -} -``` - -This may also be useful for publishers who want to account for different discrepancies with different bidders. - -#### Targeting - -Targeting refers to strings which are sent to the adserver to -[make header bidding possible](http://prebid.org/overview/intro.html#how-does-prebid-work). - -`request.ext.prebid.targeting` is an optional property which causes Prebid Server -to set these params on the response at `response.seatbid[i].bid[j].ext.prebid.targeting`. - -**Request format** (optional param `request.ext.prebid.targeting`) - -``` -{ - "ext": { - "prebid": { - "targeting": { - "pricegranularity": { - "precision": 2, - "ranges": [{ - "max": 20.00, - "increment": 0.10 // This is equivalent to the deprecated "pricegranularity": "medium" - }] - }, - "includewinners": false, // Optional param defaulting to true - "includebidderkeys": false // Optional param defaulting to true - "includeformat": false // Optional param defaulting to false - } - } - } -} -``` -The list of price granularity ranges must be given in order of increasing `max` values. If `precision` is omitted, it will default to `2`. The minimum of a range will be 0 or the previous `max`. Any cmp above the largest `max` will go in the `max` pricebucket. - -For backwards compatibility the following strings will also be allowed as price granularity definitions. There is no guarantee that these will be honored in the future. "One of ['low', 'med', 'high', 'auto', 'dense']" See [price granularity definitions](http://prebid.org/prebid-mobile/adops-price-granularity.html) - -One of "includewinners" or "includebidderkeys" must be true (both default to true if unset). If both were false, then no targeting keys would be set, which is better configured by omitting targeting altogether. - -The parameter "includeformat" indicates the type of the bid (banner, video, etc) for multiformat requests. It will add the key `hb_format` and/or `hb_format_{bidderName}` as per "includewinners" and "includebidderkeys" above. - -MediaType PriceGranularity (PBS-Java only) - when a single OpenRTB request contains multiple impressions with different mediatypes, or a single impression supports multiple formats, the different mediatypes may need different price granularities. If `mediatypepricegranularity` is present, `pricegranularity` would only be used for any mediatypes not specified. - -``` -{ - "ext": { - "prebid": { - "targeting": { - "mediatypepricegranularity": { - "banner": { - "ranges": [ - {"max": 20, "increment": 0.5} - ] - }, - "video": { - "ranges": [ - {"max": 10, "increment": 1}, - {"max": 20, "increment": 2}, - {"max": 50, "increment": 5} - ] - } - } - }, - "includewinners": true - } - } -} -``` - -**Response format** (returned in `bid.ext.prebid.targeting`) - -``` -{ - "seatbid": [{ - "bid": [{ - ... - "ext": { - "prebid": { - "targeting": { - "hb_bidder_{bidderName}": "The seatbid.seat which contains this bid", - "hb_size_{bidderName}": "A string like '300x250' using bid.w and bid.h for this bid", - "hb_pb_{bidderName}": "The bid.cpm, rounded down based on the price granularity." - } - } - } - }] - }] -} -``` - -The winning bid for each `request.imp[i]` will also contain `hb_bidder`, `hb_size`, and `hb_pb` -(with _no_ {bidderName} suffix). To prevent these keys, set `request.ext.prebid.targeting.includeWinners` to false. - -**NOTE**: Targeting keys are limited to 20 characters. If {bidderName} is too long, the returned key -will be truncated to only include the first 20 characters. - -#### Cookie syncs - -Each Bidder should receive their own ID in the `request.user.buyeruid` property. -Prebid Server has three ways to populate this field. In order of priority: - -1. If the request payload contains `request.user.buyeruid`, then that value will be sent to all Bidders. -In most cases, this is probably a bad idea. - -2. The request payload can store a `buyeruid` for each Bidder by defining `request.user.ext.prebid.buyeruids` like so: - -``` -{ - "user": { - "ext": { - "prebid": { - "buyeruids": { - "appnexus": "some-appnexus-id", - "rubicon": "some-rubicon-id" - } - } - } - } -} -``` - -Prebid Server's core logic will preprocess the request so that each Bidder sees their own value in the `request.user.buyeruid` field. - -3. Prebid Server will use its Cookie to map IDs for each Bidder. - -If you're using [Prebid.js](https://github.com/prebid/Prebid.js), this is happening automatically. - -If you're using another client, you can populate the Cookie of the Prebid Server host with User IDs -for each Bidder by using the `/cookie_sync` endpoint, and calling the URLs that it returns in the response. - -#### Native Request - -For each native request, the `assets` object's `id` field must not be defined. Prebid Server will set this automatically, using the index of the asset in the array as the ID. - - -#### Bidder Aliases - -Requests can define Bidder aliases if they want to refer to a Bidder by a separate name. -This can be used to request bids from the same Bidder with different params. For example: - -``` -{ - "imp": [{ - "id": "some-impression-id", - "video": { - "mimes": ["video/mp4"] - }, - "ext": { - "appnexus": { - "placementId": 123 - }, - "districtm": { - "placementId": 456 - } - } - }], - "ext": { - "prebid": { - "aliases": { - "districtm": "appnexus" - } - } - } -} -``` - -For all intents and purposes, the alias will be treated as another Bidder. This new Bidder will behave exactly -like the original, except that the Response will contain separate SeatBids, and any Targeting keys -will be formed using the alias' name. - -If an alias overlaps with a core Bidder's name, then the alias will take precedence. -This prevents breaking API changes as new Bidders are added to the project. - -For example, if the Request defines an alias like this: - -``` - "aliases": { - "appnexus": "rubicon" - } -``` - -then any `imp.ext.appnexus` params will actually go to the **rubicon** adapter. -It will become impossible to fetch bids from AppNexus within that Request. - -#### Bidder Response Times - -`response.ext.responsetimemillis.{bidderName}` tells how long each bidder took to respond. -These can help quantify the performance impact of "the slowest bidder." - -#### Bidder Errors - -`response.ext.errors.{bidderName}` contains messages which describe why a request may be "suboptimal". -For example, suppose a `banner` and a `video` impression are offered to a bidder -which only supports `banner`. - -In cases like these, the bidder can ignore the `video` impression and bid on the `banner` one. -However, the publisher can improve performance by only offering impressions which the bidder supports. - -For example, a request may return this in `response.ext` - -``` -{ - "ext": { - "errors": { - "appnexus": [{ - "code": 2, - "message": "A hybrid Banner/Audio Imp was offered, but Appnexus doesn't support Audio." - }], - "rubicon": [{ - "code": 1, - "message": "The request exceeded the timeout allocated" - }] - } - } -} -``` - -The codes currently defined are: - -``` -0 NoErrorCode -1 TimeoutCode -2 BadInputCode -3 BadServerResponseCode -999 UnknownErrorCode -``` - -#### Debugging - -`response.ext.debug.httpcalls.{bidder}` will be populated **only if** `request.test` **was set to 1**. - -This contains info about every request and response sent by the bidder to its server. -It is only returned on `test` bids for performance reasons, but may be useful during debugging. - -`response.ext.debug.resolvedrequest` will be populated **only if** `request.test` **was set to 1**. - -This contains the request after the resolution of stored requests and implicit information (e.g. site domain, device user agent). - -#### Stored Requests - -`request.imp[i].ext.prebid.storedrequest` incorporates a [Stored Request](../../developers/stored-requests.md) from the server. - -A typical `storedrequest` value looks like this: - -``` -{ - "imp": [{ - "ext": { - "prebid": { - "storedrequest": { - "id": "some-id" - } - } - } - }] -} -``` - -For more information, see the docs for [Stored Requests](../../developers/stored-requests.md). - -#### Cache bids - -Bids can be temporarily cached on the server by sending the following data as `request.ext.prebid.cache`: - -``` -{ - "ext": { - "prebid": { - "cache": { - "bids": {}, - "vastxml": {} - } - } - } -} -``` - -Both `bids` and `vastxml` are optional, but one of the two is required if you want to cache bids. This property will have no effect -unless `request.ext.prebid.targeting` is also set in the request. - -If `bids` is present, Prebid Server will make a _best effort_ to include these extra -`bid.ext.prebid.targeting` keys: - -- `hb_cache_id`: On the highest overall Bid in each Imp. -- `hb_cache_id_{bidderName}`: On the highest Bid from {bidderName} in each Imp. - -Clients _should not assume_ that these keys will exist, just because they were requested, though. -If they exist, the value will be a UUID which can be used to fetch Bid JSON from [Prebid Cache](https://github.com/prebid/prebid-cache). -They may not exist if the host company's cache is full, having connection problems, or other issues like that. - -If `vastxml` is present, PBS will try to add analogous keys `hb_uuid` and `hb_uuid_{bidderName}`. -In addition to the caveats above, these will exist _only if the relevant Bids are for Video_. -If they exist, the values can be used to fetch the bid's VAST XML from Prebid Cache directly. - -These options are mainly intended for certain limited Prebid Mobile setups, where bids cannot be cached client-side. - -#### GDPR - -Prebid Server supports the IAB's GDPR recommendations, which can be found [here](https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf). - -This adds two optional properties: - -- `request.user.ext.consent`: Is the consent string required by the IAB standards. -- `request.regs.ext.gdpr`: Is 0 if the caller believes that the user is *not* under GDPR, 1 if the user *is* under GDPR, and undefined if we're not certain. - -These fields will be forwarded to each Bidder, so they can decide how to process them. - -#### Interstitial support -Additional support for interstitials is enabled through the addition of two fields to the request: -device.ext.prebid.interstitial.minwidthperc and device.ext.interstial.minheightperc -The values will be numbers that indicate the minimum allowed size for the ad, as a percentage of the base side. For example, a width of 600 and "minwidthperc": 60 would allow ads with widths from 360 to 600 pixels inclusive. - -Example: -``` -{ - "imp": [{ - ... - "banner": { - ... - } - "instl": 1, - ... - }] - "device": { - ... - "h": 640, - "w": 320, - "ext": { - "prebid": { - "interstitial": { - "minwidthperc": 60, - "minheightperc": 60 - } - } - } - } -} -``` - -PBS receiving a request for an interstitial imp and these parameters set, it will rewrite the format object within the interstitial imp. If the format array's first object is a size, PBS will take it as the max size for the interstitial. If that size is 1x1, it will look up the device's size and use that as the max size. If the format is not present, it will also use the device size as the max size. (1x1 support so that you don't have to omit the format object to use the device size) -PBS with interstitial support will come preconfigured with a list of common ad sizes. Preferentially organized by weighing the larger and more common sizes first. But no guarantees to the ordering will be made. PBS will generate a new format list for the interstitial imp by traversing this list and picking the first 10 sizes that fall within the imp's max size and minimum percentage size. There will be no attempt to favor aspect ratios closer to the original size's aspect ratio. The limit of 10 is enforced to ensure we don't overload bidders with an overlong list. All the interstitial parameters will still be passed to the bidders, so they may recognize them and use their own size matching algorithms if they prefer. - -#### Currency Support - -To set the desired 'ad server currency', use the standard OpenRTB `cur` attribute. Note that Prebid Server only looks at the first currency in the array. - -``` - "cur": ["USD"] -``` - -If you want or need to define currency conversion rates (e.g. for currencies that your Prebid Server doesn't support), -define ext.prebid.currency.rates. (Currently supported in PBS-Java only) - -``` -"ext": { - "prebid": { - "currency": { - "rates": { - "USD": { "UAH": 24.47, "ETB": 32.04 } - } - } - } -} -``` - -If it exists, a rate defined in ext.prebid.currency.rates has the highest priority. -If a currency rate doesn't exist in the request, the external file will be used. - -#### Supply Chain Support - - -Basic supply chains are passed to Prebid Server on `source.ext.schain` and passed through to bid adapters. Prebid Server does not currently offer the ability to add a node to the supply chain. - -Bidder-specific schains (PBS-Java only): - -``` -ext.prebid.schains: [ - { bidders: ["bidderA"], schain: { SCHAIN OBJECT 1}}, - { bidders: ["*"], schain: { SCHAIN OBJECT 2}} -] -``` -In this scenario, Prebid Server sends the first schain object to `bidderA` and the second schain object to everyone else. - -If there's already an source.ext.schain and a bidder is named in ext.prebid.schains (or covered by the wildcard condition), ext.prebid.schains takes precedent. - -#### Rewarded Video (PBS-Java only) - -Rewarded video is a way to incentivize users to watch ads by giving them 'points' for viewing an ad. A Prebid Server -client can declare a given adunit as eligible for rewards by declaring `imp.ext.prebid.is_rewarded_inventory:1`. - -#### Stored Responses (PBS-Java only) - -While testing SDK and video integrations, it's important, but often difficult, to get consistent responses back from bidders that cover a range of scenarios like different CPM values, deals, etc. Prebid Server supports a debugging workflow in two ways: - -- a stored-auction-response that covers multiple bidder responses -- multiple stored-bid-responses at the bidder adapter level - -**Single Stored Auction Response ID** - -When a storedauctionresponse ID is specified: - -- the rest of the ext.prebid block is irrelevant and ignored -- nothing is sent to any bidder adapter for that imp -- the response retrieved from the stored-response-id is assumed to be the entire contents of the seatbid object corresponding to that impression. - -This request: -``` -{ - "test":1, - "tmax":500, - "id": "test-auction-id", - "app": { ... }, - "ext": { - "prebid": { - "targeting": {}, - "cache": { "bids": {} } - } - }, - "imp": [ - { - "id": "a", - "ext": { "prebid": { "storedauctionresponse": { "id": "1111111111" } } } - }, - { - "id": "b", - "ext": { "prebid": { "storedauctionresponse": { "id": "22222222222" } } } - } - ] -} -``` - -Will result in this response, assuming that the ids exist in the appropriate DB table read by Prebid Server: -``` -{ - "id": "test-auction-id", - "seatbid": [ - { - // BidderA bids from storedauctionresponse=1111111111 - // BidderA bids from storedauctionresponse=22222222 - }, - { - // BidderB bids from storedauctionresponse=1111111111 - // BidderB bids from storedauctionresponse=22222222 - } - ] -} -``` - -**Multiple Stored Bid Response IDs** - -In contrast to what's outlined above, this approach lets some real auctions take place while some bidders have test responses that still exercise bidder code. For example, this request: - -``` -{ - "test":1, - "tmax":500, - "id": "test-auction-id", - "app": { ... }, - "ext": { - "prebid": { - "targeting": {}, - "cache": { "bids": {} } - } - }, - "imp": [ - { - "id": "a", - "ext": { - "prebid": { - "storedbidresponse": [ - { "bidder": "BidderA", "id": "333333" }, - { "bidder": "BidderB", "id": "444444" }, - ] - } - } - }, - { - "id": "b", - "ext": { - "prebid": { - "storedbidresponse": [ - { "bidder": "BidderA", "id": "5555555" }, - { "bidder": "BidderB", "id": "6666666" }, - ] - } - } - } - ] -} -``` -Could result in this response: - -``` -{ - "id": "test-auction-id", - "seatbid": [ - { - "bid": [ - // contents of storedbidresponse=3333333 as parsed by bidderA adapter - // contents of storedbidresponse=5555555 as parsed by bidderA adapter - ] - }, - { - // contents of storedbidresponse=4444444 as parsed by bidderB adapter - // contents of storedbidresponse=6666666 as parsed by bidderB adapter - } - ] -} -``` - -Setting up the storedresponse DB entries is the responsibility of each Prebid Server host company. - -See Prebid.org troubleshooting pages for how to utilize this feature within the context of the browser. - - -#### User IDs (PBS-Java only) - -Prebid Server adapters can support the [Prebid.js User ID modules](http://prebid.org/dev-docs/modules/userId.html) by reading the following extensions and passing them through to their server endpoints: - -``` -{ - "user": { - "ext": { - "eids": [{ - "source": "adserver.org", - "uids": [{ - "id": "111111111111", - "ext": { - "rtiPartner": "TDID" - } - }] - }, - { - "source": "pubcommon", - "id":"11111111" - } - ], - "digitrust": { - "id": "11111111111", - "keyv": 4 - } - } - } -} -``` - -#### First Party Data Support (PBS-Java only) - -This is the Prebid Server version of the Prebid.js First Party Data feature. It's a standard way for the page (or app) to supply first party data and control which bidders have access to it. - -It specifies where in the OpenRTB request non-standard attributes should be passed. For example: - -``` -{ - "ext": { - "prebid": { - "data": { "bidders": [ "rubicon", "appnexus" ] } // these are the bidders allowed to see protected data - } - }, - "site": { - "keywords": "", - "search": "", - "ext": { - data: { GLOBAL CONTEXT DATA } // only seen by bidders named in ext.prebid.data.bidders[] - } - }, - "user": { - "keywords": "", - "gender": "", - "yob": 1999, - "geo": {}, - "ext": { - data: { GLOBAL USER DATA } // only seen by bidders named in ext.prebid.data.bidders[] - } - }, - "imp": [ - "ext": { - "context": { - "keywords": "", - "search": "", - "data": { ADUNIT SPECFIC CONTEXT DATA } // can be seen by all bidders - } - } - ] -``` - -Prebid Server enforces the data permissioning - -So before passing the values to the bidder adapters, core will: - -1. check for ext.prebid.data.bidders -1. if it exists, store it locally, but remove it from the OpenRTB before being sent to the adapters -1. As the OpenRTB request is being sent to each adapter: - 1. if ext.prebid.data.bidders exists in the original request, and this bidder is on the list then copy site.ext.data, app.ext.data, and user.ext.data to their bidder request -- otherwise don't copy those blocks - 1. copy other objects as normal - -Each adapter must be coded to read the values from these locations and pass it to their endpoints appropriately. - -### OpenRTB Ambiguities - -This section describes the ways in which Prebid Server **implements** OpenRTB spec ambiguous parts. - -- `request.cur`: If `request.cur` is not specified in the bid request, Prebid Server will consider it as being `USD` whereas OpenRTB spec doesn't mention any default currency for bid request. -```request.cur: ['USD'] // Default value if not set``` - - -### OpenRTB Differences - -This section describes the ways in which Prebid Server **breaks** the OpenRTB spec. - -#### Allowed Bidders - -Prebid Server returns a 400 on requests which define `wseat` or `bseat`. -We may add support for these in the future, if there's compelling need. - -Instead, an impression is only offered to a bidder if `bidrequest.imp[i].ext.{bidderName}` exists. - -This supports publishers who want to sell different impressions to different bidders. - -#### Deprecated Properties - -This endpoint returns a 400 if the request contains deprecated properties (e.g. `imp.wmin`, `imp.hmax`). - -The error message in the response should describe how to "fix" the request to make it legal. -If the message is unclear, please [log an issue](https://github.com/prebid/prebid-server/issues) -or [submit a pull request](https://github.com/prebid/prebid-server/pulls) to improve it. - -#### Determining Bid Security (http/https) - -In the OpenRTB spec, `request.imp[i].secure` says: - -> Flag to indicate if the impression requires secure HTTPS URL creative assets and markup, -> where 0 = non-secure, 1 = secure. If omitted, the secure state is unknown, but non-secure -> HTTP support can be assumed. - -In Prebid Server, an `https` request which does not define `secure` will be forwarded to Bidders with a `1`. -Publishers who run `https` sites and want insecure ads can still set this to `0` explicitly. - -### See also - -- [The OpenRTB 2.5 spec](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf) diff --git a/docs/endpoints/setuid.md b/docs/endpoints/setuid.md deleted file mode 100644 index c1746806371..00000000000 --- a/docs/endpoints/setuid.md +++ /dev/null @@ -1,26 +0,0 @@ -# Saving User Syncs - -This endpoint is used during cookie syncs. For technical details, see the -[Cookie Sync developer docs](../developers/cookie-syncs.md). - -## `GET /setuid` - -This endpoint saves a UserID for a Bidder in the Cookie. Saved IDs will be recognized for 7 days before being considered "stale" and being re-synced. - -### Query Params - -- `bidder`: The FamilyName of the [Usersyncer](../../usersync/usersync.go) which is being synced. -- `uid`: The ID which the Bidder uses to recognize this user. If undefined, the UID for `bidder` will be deleted. -- `gdpr`: This should be `1` if GDPR is in effect, `0` if not, and undefined if the caller isn't sure -- `gdpr_consent`: This is required if `gdpr` is one, and optional (but encouraged) otherwise. If present, it should be an [unpadded base64-URL](https://tools.ietf.org/html/rfc4648#page-7) encoded [Vendor Consent String](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-). - -If the `gdpr` and `gdpr_consent` params are included, this endpoint will _not_ write a cookie unless: - -1. The Vendor ID set by the Prebid Server host company has permission to save cookies for that user. -2. The Prebid Server host company did not configure it to run with GDPR support. - -If in doubt, contact the company hosting Prebid Server and ask if they're GDPR-ready. - -### Sample request - -`GET http://prebid.site.com/setuid?bidder=adnxs&uid=12345&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw` diff --git a/docs/endpoints/status.md b/docs/endpoints/status.md deleted file mode 100644 index 0c252397423..00000000000 --- a/docs/endpoints/status.md +++ /dev/null @@ -1,9 +0,0 @@ -## `GET /status` - -This endpoint will return a 2xx response whenever Prebid Server is ready to serve requests. -Its exact response can be [configured](../developers/configuration.md) with the `status_response` -config option. For example, in `pbs.yaml`: - -```yaml -status_response: "ok" -``` From ceaf883f3d94332ef8678e8e1694e48dbce56020 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Tue, 25 Aug 2020 15:05:13 -0700 Subject: [PATCH 178/603] Fix bid dedup (#1456) Co-authored-by: Veronika Solovei --- exchange/exchange.go | 2 +- exchange/exchange_test.go | 72 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index e465a78389b..1fbdfe8ea67 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -660,7 +660,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques // An older bid from a different seatBid we've already finished with oldSeatBid := (seatBids)[dupe.bidderName] if len(oldSeatBid.bids) == 1 { - seatBidsToRemove = append(seatBidsToRemove, bidderName) + seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index a6f69f70c59..d1531237688 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1619,6 +1619,78 @@ func TestBidRejectionErrors(t *testing.T) { } } +func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + requestExt := newExtRequestTranslateCategories(nil) + + targData := &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: true, + } + + requestExt.Prebid.Targeting.DurationRangeSec = []int{30} + requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false + + cats1 := []string{"IAB1-3"} + cats2 := []string{"IAB1-4"} + + bidApn1 := openrtb.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bidApn2 := openrtb.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} + + bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0} + + innerBidsApn1 := []*pbsOrtbBid{ + &bid1_Apn1, + } + + innerBidsApn2 := []*pbsOrtbBid{ + &bid1_Apn2, + } + + for i := 1; i < 10; i++ { + adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + + seatBidApn1 := pbsOrtbSeatBid{innerBidsApn1, "USD", nil, nil} + bidderNameApn1 := openrtb_ext.BidderName("appnexus1") + + seatBidApn2 := pbsOrtbSeatBid{innerBidsApn2, "USD", nil, nil} + bidderNameApn2 := openrtb_ext.BidderName("appnexus2") + + adapterBids[bidderNameApn1] = &seatBidApn1 + adapterBids[bidderNameApn2] = &seatBidApn2 + + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + + assert.NoError(t, err, "Category mapping error should be empty") + assert.Len(t, rejections, 1, "There should be 1 bid rejection message") + assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_idApn(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") + assert.Len(t, bidCategory, 1, "Bidders category mapping should have only one element") + + var resultBid string + for bidId := range bidCategory { + resultBid = bidId + } + + if resultBid == "bid_idApn1" { + assert.Nil(t, seatBidApn2.bids, "Appnexus_2 seat bid should not have any bids back") + assert.Len(t, seatBidApn1.bids, 1, "Appnexus_1 seat bid should have only one back") + + } else { + assert.Nil(t, seatBidApn1.bids, "Appnexus_1 seat bid should not have any bids back") + assert.Len(t, seatBidApn2.bids, 1, "Appnexus_2 seat bid should have only one back") + + } + + } + +} + func TestUpdateRejections(t *testing.T) { rejections := []string{} From 1c9b521f0f98086b6f27c00762353d78977bc54f Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 27 Aug 2020 15:36:30 +0100 Subject: [PATCH 179/603] consumable: Correct width and height reported in response. (#1459) Prebid Server now responds with the width and height specified in the Bid Response from Consumable. Previously it would reuse the width and height specified in the Bid Request. That older behaviour was ported from an older version of the prebid.js adapter but is no longer valid. --- adapters/consumable/consumable.go | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index 243f1b8000b..ff7451f15f7 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -69,6 +69,8 @@ type decision struct { CreativeID string `json:"creativeId,omitempty"` Contents []contents `json:"contents"` ImpressionUrl *string `json:"impressionUrl,omitempty"` + Width uint64 `json:"width,omitempty"` // Consumable extension, not defined by Adzerk + Height uint64 `json:"height,omitempty"` // Consumable extension, not defined by Adzerk } type contents struct { @@ -241,23 +243,13 @@ func (a *ConsumableAdapter) MakeBids( for impID, decision := range serverResponse.Decisions { if decision.Pricing != nil && decision.Pricing.ClearPrice != nil { - - imp := getImp(impID, internalRequest.Imp) - if imp == nil { - errors = append(errors, &errortypes.BadServerResponse{ - Message: fmt.Sprintf( - "ignoring bid id=%s, request doesn't contain any impression with id=%s", internalRequest.ID, impID), - }) - continue - } - bid := openrtb.Bid{} bid.ID = internalRequest.ID bid.ImpID = impID bid.Price = *decision.Pricing.ClearPrice bid.AdM = retrieveAd(decision) - bid.W = imp.Banner.Format[0].W // TODO: Review to check if this is correct behaviour - bid.H = imp.Banner.Format[0].H + bid.W = decision.Width + bid.H = decision.Height bid.CrID = strconv.FormatInt(decision.AdID, 10) bid.Exp = 30 // TODO: Check this is intention of TTL @@ -279,15 +271,6 @@ func (a *ConsumableAdapter) MakeBids( return bidderResponse, errors } -func getImp(impId string, imps []openrtb.Imp) *openrtb.Imp { - for _, imp := range imps { - if imp.ID == impId { - return &imp - } - } - return nil -} - func extractExtensions(impression openrtb.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(impression.Ext, &bidderExt); err != nil { From 1f8749789d261d23858db3724d01aaad6417c503 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 27 Aug 2020 13:35:37 -0400 Subject: [PATCH 180/603] Panics happen when left with zero length []Imp (#1462) --- adapters/info.go | 8 +++ adapters/info_test.go | 142 +++++++++++++++++++++++++++++++----------- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/adapters/info.go b/adapters/info.go index 732ae85589b..982827c90fb 100644 --- a/adapters/info.go +++ b/adapters/info.go @@ -37,6 +37,7 @@ type InfoAwareBidder struct { func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) { var allowedMediaTypes parsedSupports + if request.Site != nil { if !i.info.site.enabled { return nil, []error{BadInput("this bidder does not support site requests")} @@ -56,7 +57,14 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *Ext // To avoid allocating new arrays and copying in the normal case, we'll make one pass to // see if any imps need to be removed, and another to do the removing if necessary. numToFilter, errs := i.pruneImps(request.Imp, allowedMediaTypes) + + // If all imps in bid request come with unsupported media types, exit + if numToFilter == len(request.Imp) { + return nil, append(errs, BadInput("Bid request didn't contain media types supported by the bidder")) + } + if numToFilter != 0 { + // Filter out imps with unsupported media types filteredImps, newErrs := i.filterImps(request.Imp, numToFilter) request.Imp = filteredImps errs = append(errs, newErrs...) diff --git a/adapters/info_test.go b/adapters/info_test.go index 9c0dd16babb..ce0f88696db 100644 --- a/adapters/info_test.go +++ b/adapters/info_test.go @@ -24,6 +24,7 @@ func TestAppNotSupported(t *testing.T) { } constrained := adapters.EnforceBidderInfo(bidder, info) bids, errs := constrained.MakeRequests(&openrtb.BidRequest{ + Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}}, App: &openrtb.App{}, }, &adapters.ExtraRequestInfo{}) if !assert.Len(t, errs, 1) { @@ -45,6 +46,7 @@ func TestSiteNotSupported(t *testing.T) { } constrained := adapters.EnforceBidderInfo(bidder, info) bids, errs := constrained.MakeRequests(&openrtb.BidRequest{ + Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}}, Site: &openrtb.Site{}, }, &adapters.ExtraRequestInfo{}) if !assert.Len(t, errs, 1) { @@ -69,48 +71,111 @@ func TestImpFiltering(t *testing.T) { } constrained := adapters.EnforceBidderInfo(bidder, info) - _, errs := constrained.MakeRequests(&openrtb.BidRequest{ - Imp: []openrtb.Imp{ - { - ID: "imp-1", - Video: &openrtb.Video{}, + + testCases := []struct { + description string + inBidRequest *openrtb.BidRequest + expectedErrors []error + expectedImpLen int + }{ + { + description: "Empty Imp array. MakeRequest() call not expected", + inBidRequest: &openrtb.BidRequest{ + Imp: []openrtb.Imp{}, + Site: &openrtb.Site{}, }, - { - Native: &openrtb.Native{}, + expectedErrors: []error{ + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, }, - { - ID: "imp-2", - Video: &openrtb.Video{}, - Native: &openrtb.Native{}, + expectedImpLen: 0, + }, + { + description: "Sole imp in bid request is of wrong media type. MakeRequest() call not expected", + inBidRequest: &openrtb.BidRequest{ + Imp: []openrtb.Imp{{ID: "imp-1", Video: &openrtb.Video{}}}, + App: &openrtb.App{}, }, - { - Banner: &openrtb.Banner{}, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, }, + expectedImpLen: 0, + }, + { + description: "All imps in bid request of wrong media type, MakeRequest() call not expected", + inBidRequest: &openrtb.BidRequest{ + Imp: []openrtb.Imp{ + {ID: "imp-1", Video: &openrtb.Video{}}, + {ID: "imp-2", Native: &openrtb.Native{}}, + {ID: "imp-3", Audio: &openrtb.Audio{}}, + }, + App: &openrtb.App{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[2] uses audio, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, + }, + expectedImpLen: 0, + }, + { + description: "Some imps with correct media type, MakeRequest() call expected", + inBidRequest: &openrtb.BidRequest{ + Imp: []openrtb.Imp{ + { + ID: "imp-1", + Video: &openrtb.Video{}, + }, + { + Native: &openrtb.Native{}, + }, + { + ID: "imp-2", + Video: &openrtb.Video{}, + Native: &openrtb.Native{}, + }, + { + Banner: &openrtb.Banner{}, + }, + }, + Site: &openrtb.Site{}, + }, + expectedErrors: []error{ + &errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[2] uses native, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[3] uses banner, but this bidder doesn't support it"}, + &errortypes.BadInput{Message: "request.imp[1] has no supported MediaTypes. It will be ignored"}, + &errortypes.BadInput{Message: "request.imp[3] has no supported MediaTypes. It will be ignored"}, + }, + expectedImpLen: 2, + }, + { + description: "All imps with correct media type, MakeRequest() call expected", + inBidRequest: &openrtb.BidRequest{ + Imp: []openrtb.Imp{ + {ID: "imp-1", Video: &openrtb.Video{}}, + {ID: "imp-2", Video: &openrtb.Video{}}, + }, + Site: &openrtb.Site{}, + }, + expectedErrors: nil, + expectedImpLen: 2, }, - Site: &openrtb.Site{}, - }, &adapters.ExtraRequestInfo{}) - if !assert.Len(t, errs, 6) { - return } - assert.EqualError(t, errs[0], "request.imp[1] uses native, but this bidder doesn't support it") - assert.EqualError(t, errs[1], "request.imp[2] uses native, but this bidder doesn't support it") - assert.EqualError(t, errs[2], "request.imp[3] uses banner, but this bidder doesn't support it") - assert.EqualError(t, errs[3], "request.imp[1] has no supported MediaTypes. It will be ignored") - assert.EqualError(t, errs[4], "request.imp[3] has no supported MediaTypes. It will be ignored") - assert.EqualError(t, errs[5], "mock MakeRequests error") - assert.IsType(t, &errortypes.BadInput{}, errs[0]) - assert.IsType(t, &errortypes.BadInput{}, errs[1]) - assert.IsType(t, &errortypes.BadInput{}, errs[2]) - assert.IsType(t, &errortypes.BadInput{}, errs[3]) - assert.IsType(t, &errortypes.BadInput{}, errs[4]) - req := bidder.gotRequest - if !assert.Len(t, req.Imp, 2) { - return + for _, test := range testCases { + actualAdapterRequests, actualErrs := constrained.MakeRequests(test.inBidRequest, &adapters.ExtraRequestInfo{}) + + // Assert the request.Imp slice was correctly filtered and if MakeRequest() was called by asserting + // the corresponding error messages were returned + for i, expectedErr := range test.expectedErrors { + assert.EqualError(t, expectedErr, actualErrs[i].Error(), "Test failed. Error[%d] in error list mismatch: %s", i, test.description) + } + + // Extra MakeRequests() call check: our mockBidder returns an adapter request for every imp + assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect lenght of filtered imps: %s", test.description) } - assert.Equal(t, "imp-1", req.Imp[0].ID) - assert.Equal(t, "imp-2", req.Imp[1].ID) - assert.Nil(t, req.Imp[1].Native) } type mockBidder struct { @@ -118,8 +183,13 @@ type mockBidder struct { } func (m *mockBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - m.gotRequest = request - return nil, []error{errors.New("mock MakeRequests error")} + var adapterRequests []*adapters.RequestData + + for i := 0; i < len(request.Imp); i++ { + adapterRequests = append(adapterRequests, &adapters.RequestData{}) + } + + return adapterRequests, nil } func (m *mockBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { From 292df1f3f5da623c6c140542510da026b1c3cbd2 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 27 Aug 2020 14:19:25 -0400 Subject: [PATCH 181/603] Add Scheme Option To External Cache URL (#1460) --- config/config.go | 14 +- config/config_test.go | 17 +++ endpoints/openrtb2/video_auction_test.go | 4 +- exchange/auction_test.go | 17 ++- exchange/exchange.go | 61 +++++--- exchange/exchange_test.go | 141 +++++++++++++++++- .../exchangetest/targeting-cache-vast.json | 2 +- .../exchangetest/targeting-cache-zero.json | 2 +- prebid_cache_client/client.go | 30 ++-- prebid_cache_client/client_test.go | 74 +++++---- 10 files changed, 284 insertions(+), 78 deletions(-) diff --git a/config/config.go b/config/config.go index e3b7d8ebda0..c2e7dfd8f3f 100755 --- a/config/config.go +++ b/config/config.go @@ -136,6 +136,10 @@ func (data *ExternalCache) validate(errs configErrors) configErrors { return errs } + if data.Scheme != "" && data.Scheme != "http" && data.Scheme != "https" { + return append(errs, errors.New("External cache Scheme must be http or https if specified")) + } + // Either host or path or both not empty, validate. if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" { return append(errs, errors.New("External cache Host and Path must both be specified")) @@ -486,13 +490,14 @@ type DataCache struct { TTLSeconds int `mapstructure:"ttl_seconds"` } -// Data type where we store the external cache URL elements. This is completely unrelated to type Cache struct defined afterwards, because -// the latter is used for internal cache URL while the following contains information of the external cache URL. +// ExternalCache configures the externally accessible cache url. type ExternalCache struct { - Host string `mapstructure:"host"` - Path string `mapstructure:"path"` + Scheme string `mapstructure:"scheme"` + Host string `mapstructure:"host"` + Path string `mapstructure:"path"` } +// Cache configures the url used internally by Prebid Server to communicate with Prebid Cache. type Cache struct { Scheme string `mapstructure:"scheme"` Host string `mapstructure:"host"` @@ -734,6 +739,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("cache.default_ttl_seconds.video", 0) v.SetDefault("cache.default_ttl_seconds.native", 0) v.SetDefault("cache.default_ttl_seconds.audio", 0) + v.SetDefault("external_cache.scheme", "") v.SetDefault("external_cache.host", "") v.SetDefault("external_cache.path", "") v.SetDefault("recaptcha_secret", "") diff --git a/config/config_test.go b/config/config_test.go index 3da3f72137b..40589ac7b23 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -90,6 +90,21 @@ func TestExternalCacheURLValidate(t *testing.T) { data: ExternalCache{Host: "http://", Path: ""}, expErrors: 1, }, + { + desc: "Scheme Invalid", + data: ExternalCache{Scheme: "invalid", Host: "www.google.com", Path: "/path/v1"}, + expErrors: 1, + }, + { + desc: "Scheme HTTP", + data: ExternalCache{Scheme: "http", Host: "www.google.com", Path: "/path/v1"}, + expErrors: 0, + }, + { + desc: "Scheme HTTPS", + data: ExternalCache{Scheme: "https", Host: "www.google.com", Path: "/path/v1"}, + expErrors: 0, + }, } for _, test := range testCases { var errs configErrors @@ -150,6 +165,7 @@ cache: host: prebidcache.net query: uuid=%PBS_CACHE_UUID% external_cache: + scheme: https host: www.externalprebidcache.net path: /endpoints/cache http_client: @@ -307,6 +323,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http") cmpStrings(t, "cache.host", cfg.CacheURL.Host, "prebidcache.net") cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%") + cmpStrings(t, "external_cache.scheme", cfg.ExtCacheURL.Scheme, "https") cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net") cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "/endpoints/cache") cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10) diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 534db3c79e2..78715f5c87d 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1259,8 +1259,8 @@ func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_cli return []string{}, []error{} } -func (m *mockCacheClient) GetExtCacheData() (string, string) { - return "", "" +func (m *mockCacheClient) GetExtCacheData() (scheme string, host string, path string) { + return "", "", "" } type mockVideoStoredReqFetcher struct { diff --git a/exchange/auction_test.go b/exchange/auction_test.go index e23c45cb494..9f24682bfc3 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -298,22 +298,27 @@ type pbsBid struct { Bidder openrtb_ext.BidderName `json:"bidder"` } -type mockCache struct { - items []prebid_cache_client.Cacheable -} - type cacheComparator struct { freq int expectedKeys []string actualKeys []string } -func (c *mockCache) GetExtCacheData() (string, string) { - return "", "" +type mockCache struct { + scheme string + host string + path string + items []prebid_cache_client.Cacheable +} + +func (c *mockCache) GetExtCacheData() (scheme string, host string, path string) { + return c.scheme, c.host, c.path } + func (c *mockCache) GetPutUrl() string { return "" } + func (c *mockCache) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) { c.items = values return []string{"", "", "", "", ""}, nil diff --git a/exchange/exchange.go b/exchange/exchange.go index 1fbdfe8ea67..59e876697cf 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -8,6 +8,7 @@ import ( "fmt" "math/rand" "net/http" + "net/url" "runtime/debug" "sort" "strconv" @@ -107,7 +108,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque shouldCacheBids, shouldCacheVAST := getExtCacheInfo(requestExt) targData := getExtTargetData(requestExt, shouldCacheBids, shouldCacheVAST) if targData != nil { - targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() + _, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() } debugInfo := getDebugInfo(bidRequest, requestExt) @@ -814,24 +815,50 @@ func (e *exchange) makeBid(Bids []*pbsOrtbBid, adapter openrtb_ext.BidderName, a // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL -func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auc *auction) (openrtb_ext.ExtBidPrebidCacheBids, bool) { - var cacheInfo openrtb_ext.ExtBidPrebidCacheBids - var cacheUUID string - var found bool = false - - if auc != nil { - var extCacheHost, extCachePath string - if cacheUUID, found = auc.cacheIds[bid.bid]; found { - cacheInfo.CacheId = cacheUUID - extCacheHost, extCachePath = e.cache.GetExtCacheData() - cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID - } else if cacheUUID, found = auc.vastCacheIds[bid.bid]; found { - cacheInfo.CacheId = cacheUUID - extCacheHost, extCachePath = e.cache.GetExtCacheData() - cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID +func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) { + uuid, found := findCacheID(bid, auction) + + if found { + cacheInfo.CacheId = uuid + cacheInfo.Url = buildCacheURL(e.cache, uuid) + } + + return +} + +func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) { + if bid != nil && bid.bid != nil && auction != nil { + if id, found := auction.cacheIds[bid.bid]; found { + return id, true + } + + if id, found := auction.vastCacheIds[bid.bid]; found { + return id, true } } - return cacheInfo, found + + return "", false +} + +func buildCacheURL(cache prebid_cache_client.Client, uuid string) string { + scheme, host, path := cache.GetExtCacheData() + + if host == "" || path == "" { + return "" + } + + query := url.Values{"uuid": []string{uuid}} + cacheURL := url.URL{ + Scheme: scheme, + Host: host, + Path: path, + RawQuery: query.Encode(), + } + cacheURL.Query() + + // URLs without a scheme will begin with //, in which case we + // want to trim it off to keep compatbile with current behavior. + return strings.TrimPrefix(cacheURL.String(), "//") } func listBiddersWithRequests(cleanRequests map[openrtb_ext.BidderName]*openrtb.BidRequest) []openrtb_ext.BidderName { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index d1531237688..efabb845211 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -274,9 +274,10 @@ func TestDebugBehaviour(t *testing.T) { } } -func TestGetBidCacheInfo(t *testing.T) { +func TestGetBidCacheInfoEndToEnd(t *testing.T) { testUUID := "CACHE_UUID_1234" - testExternalCacheHost := "https://www.externalprebidcache.net" + testExternalCacheScheme := "https" + testExternalCacheHost := "www.externalprebidcache.net" testExternalCachePath := "endpoints/cache" /* 1) An adapter */ @@ -292,8 +293,9 @@ func TestGetBidCacheInfo(t *testing.T) { Host: "www.internalprebidcache.net", }, ExtCacheURL: config.ExternalCache{ - Host: testExternalCacheHost, - Path: testExternalCachePath, + Scheme: testExternalCacheScheme, + Host: testExternalCacheHost, + Path: testExternalCachePath, }, } adapterList := make([]openrtb_ext.BidderName, 0, 2) @@ -421,7 +423,7 @@ func TestGetBidCacheInfo(t *testing.T) { Seat: string(bidderName), Bid: []openrtb.Bid{ { - Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`), + Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`), }, }, }, @@ -446,6 +448,131 @@ func TestGetBidCacheInfo(t *testing.T) { assert.Equal(t, expCacheURL, cacheURL, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheURL) } +func TestGetBidCacheInfo(t *testing.T) { + bid := &openrtb.Bid{ID: "42"} + testCases := []struct { + description string + scheme string + host string + path string + bid *pbsOrtbBid + auction *auction + expectedFound bool + expectedCacheID string + expectedCacheURL string + }{ + { + description: "JSON Cache ID", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: true, + expectedCacheID: "anyID", + expectedCacheURL: "https://prebid.org/cache?uuid=anyID", + }, + { + description: "VAST Cache ID", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{vastCacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: true, + expectedCacheID: "anyID", + expectedCacheURL: "https://prebid.org/cache?uuid=anyID", + }, + { + description: "Cache ID Not Found", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{}, + expectedFound: false, + expectedCacheID: "", + expectedCacheURL: "", + }, + { + description: "Scheme Not Provided", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: true, + expectedCacheID: "anyID", + expectedCacheURL: "prebid.org/cache?uuid=anyID", + }, + { + description: "Host And Path Not Provided - Without Scheme", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: true, + expectedCacheID: "anyID", + expectedCacheURL: "", + }, + { + description: "Host And Path Not Provided - With Scheme", + scheme: "https", + bid: &pbsOrtbBid{bid: bid}, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: true, + expectedCacheID: "anyID", + expectedCacheURL: "", + }, + { + description: "Nil Bid", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: nil, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: false, + expectedCacheID: "", + expectedCacheURL: "", + }, + { + description: "Nil Embedded Bid", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: nil}, + auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + expectedFound: false, + expectedCacheID: "", + expectedCacheURL: "", + }, + { + description: "Nil Auction", + scheme: "https", + host: "prebid.org", + path: "cache", + bid: &pbsOrtbBid{bid: bid}, + auction: nil, + expectedFound: false, + expectedCacheID: "", + expectedCacheURL: "", + }, + } + + for _, test := range testCases { + exchange := &exchange{ + cache: &mockCache{ + scheme: test.scheme, + host: test.host, + path: test.path, + }, + } + + cacheInfo, found := exchange.getBidCacheInfo(test.bid, test.auction) + + assert.Equal(t, test.expectedFound, found, test.description+":found") + assert.Equal(t, test.expectedCacheID, cacheInfo.CacheId, test.description+":id") + assert.Equal(t, test.expectedCacheURL, cacheInfo.Url, test.description+":url") + } +} + func TestBidResponseCurrency(t *testing.T) { // Init objects cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)} @@ -2176,8 +2303,8 @@ func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Hand type wellBehavedCache struct{} -func (c *wellBehavedCache) GetExtCacheData() (string, string) { - return "www.pbcserver.com", "/pbcache/endpoint" +func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) { + return "https", "www.pbcserver.com", "/pbcache/endpoint" } func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) { diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json index f348dd1b29d..53a48c4ec69 100644 --- a/exchange/exchangetest/targeting-cache-vast.json +++ b/exchange/exchangetest/targeting-cache-vast.json @@ -67,7 +67,7 @@ "cache": { "bids": { "cacheId": "0", - "url": "www.pbcserver.com/pbcache/endpoint?uuid=0" + "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" }, "key": "", "url": "" diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json index 5130153026a..0048ea10917 100644 --- a/exchange/exchangetest/targeting-cache-zero.json +++ b/exchange/exchangetest/targeting-cache-zero.json @@ -70,7 +70,7 @@ "cache": { "bids": { "cacheId": "0", - "url": "www.pbcserver.com/pbcache/endpoint?uuid=0" + "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0" }, "key": "", "url": "" diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index a5730ce7914..1ad788300a9 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -29,8 +29,8 @@ type Client interface { // logging any relevant errors to the app logs PutJson(ctx context.Context, values []Cacheable) ([]string, []error) - // Serves the purpose of a getter that returns the host and the cache of the prebid-server URL - GetExtCacheData() (string, string) + // GetExtCacheData gets the scheme, host, and path of the externally accessible cache url. + GetExtCacheData() (scheme string, host string, path string) } type PayloadType string @@ -49,23 +49,25 @@ type Cacheable struct { func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client { return &clientImpl{ - httpClient: httpClient, - putUrl: conf.GetBaseURL() + "/cache", - externalCacheHost: extCache.Host, - externalCachePath: extCache.Path, - metrics: metrics, + httpClient: httpClient, + putUrl: conf.GetBaseURL() + "/cache", + externalCacheScheme: extCache.Scheme, + externalCacheHost: extCache.Host, + externalCachePath: extCache.Path, + metrics: metrics, } } type clientImpl struct { - httpClient *http.Client - putUrl string - externalCacheHost string - externalCachePath string - metrics pbsmetrics.MetricsEngine + httpClient *http.Client + putUrl string + externalCacheScheme string + externalCacheHost string + externalCachePath string + metrics pbsmetrics.MetricsEngine } -func (c *clientImpl) GetExtCacheData() (string, string) { +func (c *clientImpl) GetExtCacheData() (string, string, string) { path := c.externalCachePath if path == "/" { // Only the slash for the path, remove it to empty @@ -75,7 +77,7 @@ func (c *clientImpl) GetExtCacheData() (string, string) { path = "/" + path } - return c.externalCacheHost, path + return c.externalCacheScheme, c.externalCacheHost, path } func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []string, errs []error) { diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 5840d4ea564..72fd2761731 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -186,58 +186,80 @@ func TestEncodeValueToBuffer(t *testing.T) { func TestStripCacheHostAndPath(t *testing.T) { inCacheURL := config.Cache{ExpectedTimeMillis: 10} type aTest struct { - inExtCacheURL config.ExternalCache - expectedHost string - expectedPath string + inExtCacheURL config.ExternalCache + expectedScheme string + expectedHost string + expectedPath string } testInput := []aTest{ { inExtCacheURL: config.ExternalCache{ - Host: "prebid-server.prebid.org", - Path: "/pbcache/endpoint", + Scheme: "", + Host: "prebid-server.prebid.org", + Path: "/pbcache/endpoint", }, - expectedHost: "prebid-server.prebid.org", - expectedPath: "/pbcache/endpoint", + expectedScheme: "", + expectedHost: "prebid-server.prebid.org", + expectedPath: "/pbcache/endpoint", }, { inExtCacheURL: config.ExternalCache{ - Host: "prebidcache.net", - Path: "", + Scheme: "https", + Host: "prebid-server.prebid.org", + Path: "/pbcache/endpoint", }, - expectedHost: "prebidcache.net", - expectedPath: "", + expectedScheme: "https", + expectedHost: "prebid-server.prebid.org", + expectedPath: "/pbcache/endpoint", }, { inExtCacheURL: config.ExternalCache{ - Host: "", - Path: "", + Scheme: "", + Host: "prebidcache.net", + Path: "", }, - expectedHost: "", - expectedPath: "", + expectedScheme: "", + expectedHost: "prebidcache.net", + expectedPath: "", }, { inExtCacheURL: config.ExternalCache{ - Host: "prebid-server.prebid.org", - Path: "pbcache/endpoint", + Scheme: "", + Host: "", + Path: "", }, - expectedHost: "prebid-server.prebid.org", - expectedPath: "/pbcache/endpoint", + expectedScheme: "", + expectedHost: "", + expectedPath: "", }, { inExtCacheURL: config.ExternalCache{ - Host: "prebidcache.net", - Path: "/", + Scheme: "", + Host: "prebid-server.prebid.org", + Path: "pbcache/endpoint", }, - expectedHost: "prebidcache.net", - expectedPath: "", + expectedScheme: "", + expectedHost: "prebid-server.prebid.org", + expectedPath: "/pbcache/endpoint", + }, + { + inExtCacheURL: config.ExternalCache{ + Scheme: "", + Host: "prebidcache.net", + Path: "/", + }, + expectedScheme: "", + expectedHost: "prebidcache.net", + expectedPath: "", }, } for _, test := range testInput { cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{}) - cHost, cPath := cacheClient.GetExtCacheData() + scheme, host, path := cacheClient.GetExtCacheData() - assert.Equal(t, test.expectedHost, cHost) - assert.Equal(t, test.expectedPath, cPath) + assert.Equal(t, test.expectedScheme, scheme) + assert.Equal(t, test.expectedHost, host) + assert.Equal(t, test.expectedPath, path) } } From 5d13c8595b343893c2652ad135207bf0f58263c5 Mon Sep 17 00:00:00 2001 From: GammaSSP <35954362+gammassp@users.noreply.github.com> Date: Fri, 28 Aug 2020 22:54:11 +0700 Subject: [PATCH 182/603] Update gamma adapter (#1447) * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * Update gamma.go * Update config.go Remove Gamma User Sync Url from config * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * update gamma adapter * return nil when have No-Bid Signaling * add missing-adm.json * discard the bid that's missing adm * discard the bid that's missing adm * escape vast instead of encoded it * expand test coverage Co-authored-by: Easy Life --- adapters/gamma/gamma.go | 77 ++++++++++++++++--- .../exemplary/banner-and-video-and-audio.json | 15 ++-- .../exemplary/valid-full-params.json | 2 +- .../gammatest/supplemental/bad-request.json | 2 +- .../gammatest/supplemental/missing-adm.json | 76 ++++++++++++++++++ .../gammatest/supplemental/missing-param.json | 2 +- .../gammatest/supplemental/missing-zone.json | 2 +- .../supplemental/nobid-signaling.json | 56 ++++++++++++++ .../supplemental/status-forbidden.json | 2 +- .../supplemental/status-no-content.json | 2 +- 10 files changed, 215 insertions(+), 21 deletions(-) create mode 100644 adapters/gamma/gammatest/supplemental/missing-adm.json create mode 100644 adapters/gamma/gammatest/supplemental/nobid-signaling.json diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index c011954fff9..3756723598f 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -17,6 +17,27 @@ type GammaAdapter struct { URI string } +type gammaBid struct { + openrtb.Bid //base + VastXML string `json:"vastXml,omitempty"` + VastURL string `json:"vastUrl,omitempty"` +} + +type gammaSeatBid struct { + Bid []gammaBid `json:"bid"` + Group int8 `json:"group,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` +} +type gammaBidResponse struct { + ID string `json:"id"` + SeatBid []gammaSeatBid `json:"seatbid,omitempty"` + BidID string `json:"bidid,omitempty"` + Cur string `json:"cur,omitempty"` + CustomData string `json:"customdata,omitempty"` + NBR *openrtb.NoBidReasonCode `json:"nbr,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` +} + func checkParams(gammaExt openrtb_ext.ExtImpGamma) error { if gammaExt.PartnerID == "" { return &errortypes.BadInput{ @@ -180,6 +201,28 @@ func (a *GammaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } +func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb.Bid { + var bid openrtb.Bid + bid = gBid.Bid + + if mediaType == openrtb_ext.BidTypeVideo { + //Return inline VAST XML Document (Section 6.4.2) + if len(gBid.VastXML) > 0 { + if len(gBid.VastURL) > 0 { + bid.NURL = gBid.VastURL + } + bid.AdM = gBid.VastXML + } else { + return nil + } + } else { + if len(gBid.Bid.AdM) == 0 { + return nil + } + } + return &bid +} + func (a *GammaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -197,24 +240,38 @@ func (a *GammaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + var gammaResp gammaBidResponse + if err := json.Unmarshal(response.Body, &gammaResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %d. ", err), }} } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) - for _, sb := range bidResp.SeatBid { + //(Section 7.1 No-Bid Signaling) + if len(gammaResp.SeatBid) == 0 { + return nil, nil + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(gammaResp.SeatBid[0].Bid)) + errs := make([]error, 0, len(gammaResp.SeatBid[0].Bid)) + for _, sb := range gammaResp.SeatBid { for i := range sb.Bid { - bid := sb.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: getMediaTypeForImp(bidResp.ID, internalRequest.Imp), - }) + mediaType := getMediaTypeForImp(gammaResp.ID, internalRequest.Imp) + bid := convertBid(sb.Bid[i], mediaType) + if bid != nil { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: bid, + BidType: mediaType, + }) + } else { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Missing Ad Markup. Run with request.debug = 1 for more info"), + } + errs = append(errs, err) + } } } - return bidResponse, nil + return bidResponse, errs } //Adding header fields to request header diff --git a/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json b/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json index 1c92ab96ffe..4282ac32e4d 100644 --- a/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json +++ b/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json @@ -80,7 +80,7 @@ "mockResponse": { "status": 200, "body": { - "id": "test-request-id", + "id": "test-imp-video-id", "cur": "USD", "seatbid": [ { @@ -89,7 +89,10 @@ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-video-id", "price": 0.500000, - "adm": "some-test-ad", + "adm": "", + "adomain": ["sample.com"], + "vastXml": "some-test-ad", + "vastUrl": "some-test-url", "crid": "29484110", "w": 640, "h": 480 @@ -122,13 +125,15 @@ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-video-id", "price": 0.5, - "adm": "some-test-ad", + "adm": "", + "vastXml": "some-test-ad", + "vastUrl": "some-test-url", "adid": "29484110", "adomain": ["sample.com"], "cid": "958", "crid": "29484110", - "w": 1024, - "h": 576 + "w": 640, + "h": 480 }, "type": "video" } diff --git a/adapters/gamma/gammatest/exemplary/valid-full-params.json b/adapters/gamma/gammatest/exemplary/valid-full-params.json index 8fa1d3e700a..25d51a646ff 100644 --- a/adapters/gamma/gammatest/exemplary/valid-full-params.json +++ b/adapters/gamma/gammatest/exemplary/valid-full-params.json @@ -2,7 +2,7 @@ "mockBidRequest": { "id": "test-request-id", "app":{ - "id":"test-app-id", + "id":"test-app-id", "name":"test-app-name", "bundle":"test-app-bundle" }, diff --git a/adapters/gamma/gammatest/supplemental/bad-request.json b/adapters/gamma/gammatest/supplemental/bad-request.json index d460550c198..8b533f91de0 100644 --- a/adapters/gamma/gammatest/supplemental/bad-request.json +++ b/adapters/gamma/gammatest/supplemental/bad-request.json @@ -2,7 +2,7 @@ "mockBidRequest": { "id": "test-request-id", "app":{ - "id":"test-app-id", + "id":"test-app-id", "name":"test-app-name", "bundle":"test-app-bundle" }, diff --git a/adapters/gamma/gammatest/supplemental/missing-adm.json b/adapters/gamma/gammatest/supplemental/missing-adm.json new file mode 100644 index 00000000000..6b8ff64c04a --- /dev/null +++ b/adapters/gamma/gammatest/supplemental/missing-adm.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "id": "sample-id", + "zid": "sample-zone-id", + "wid": "sample-web-id" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hb.gammaplatform.com/adx/request/?id=sample-id&zid=sample-zone-id&wid=sample-web-id&bidid=test-imp-video-id&hb=pbmobile" + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "gamma", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.500000, + "crid": "29484110", + "w": 640, + "h": 360 + } + ] + } + ] + } + } + } + ], + "expectedBids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.500000, + "crid": "29484110", + "w": 640, + "h": 360 + }, + "type": "video" + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Missing Ad Markup. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gamma/gammatest/supplemental/missing-param.json b/adapters/gamma/gammatest/supplemental/missing-param.json index 32e4f9eae6d..9dac0936bd7 100644 --- a/adapters/gamma/gammatest/supplemental/missing-param.json +++ b/adapters/gamma/gammatest/supplemental/missing-param.json @@ -20,7 +20,7 @@ "bidder": { "zid": "sample-zone-id", "wid": "sample-web-id" - + } } diff --git a/adapters/gamma/gammatest/supplemental/missing-zone.json b/adapters/gamma/gammatest/supplemental/missing-zone.json index b53ddc83f98..5c84ef229ee 100644 --- a/adapters/gamma/gammatest/supplemental/missing-zone.json +++ b/adapters/gamma/gammatest/supplemental/missing-zone.json @@ -20,7 +20,7 @@ "bidder": { "id": "sample-id", "wid": "sample-web-id" - + } } diff --git a/adapters/gamma/gammatest/supplemental/nobid-signaling.json b/adapters/gamma/gammatest/supplemental/nobid-signaling.json new file mode 100644 index 00000000000..4055ad70249 --- /dev/null +++ b/adapters/gamma/gammatest/supplemental/nobid-signaling.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app":{ + "id":"test-app-id", + "name":"test-app-name", + "bundle":"test-app-bundle" + }, + + "device":{ + "ua":"test-device-ua", + "ip":"test-device-ip", + "ifa":"test-device-ifa", + "model":"test-device-model", + "os":"test-device-os", + "dnt":1 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext":{ + "bidder":{ + "id": "sample-id", + "zid": "sample-zone-id", + "wid": "sample-web-id" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hb.gammaplatform.com/adx/request/?id=sample-id&zid=sample-zone-id&wid=sample-web-id&bidid=test-imp-id&hb=pbmobile&device_ip=test-device-ip&device_model=test-device-model&device_os=test-device-os&device_ua=test-device-ua&device_ifa=test-device-ifa&app_id=test-app-id&app_bundle=test-app-bundle&app_name=test-app-name" + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + ] + } + } + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/gamma/gammatest/supplemental/status-forbidden.json b/adapters/gamma/gammatest/supplemental/status-forbidden.json index 950a45ec0c9..3a30b210f4f 100644 --- a/adapters/gamma/gammatest/supplemental/status-forbidden.json +++ b/adapters/gamma/gammatest/supplemental/status-forbidden.json @@ -2,7 +2,7 @@ "mockBidRequest": { "id": "test-request-id", "app":{ - "id":"test-app-id", + "id":"test-app-id", "name":"test-app-name", "bundle":"test-app-bundle" }, diff --git a/adapters/gamma/gammatest/supplemental/status-no-content.json b/adapters/gamma/gammatest/supplemental/status-no-content.json index 6c7878a86c9..045fb939ced 100644 --- a/adapters/gamma/gammatest/supplemental/status-no-content.json +++ b/adapters/gamma/gammatest/supplemental/status-no-content.json @@ -2,7 +2,7 @@ "mockBidRequest": { "id": "test-request-id", "app":{ - "id":"test-app-id", + "id":"test-app-id", "name":"test-app-name", "bundle":"test-app-bundle" }, From ebdf997cc1c010cb35d5df90b2241f03c074882d Mon Sep 17 00:00:00 2001 From: gpolaert Date: Fri, 28 Aug 2020 19:40:28 +0200 Subject: [PATCH 183/603] fix: avoid unexpected EOF on gz writer (#1449) --- .../pubstack/eventchannel/eventchannel.go | 10 ++--- .../eventchannel/eventchannel_test.go | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/analytics/pubstack/eventchannel/eventchannel.go b/analytics/pubstack/eventchannel/eventchannel.go index b8dc4dd8e28..d9c2bc4117c 100644 --- a/analytics/pubstack/eventchannel/eventchannel.go +++ b/analytics/pubstack/eventchannel/eventchannel.go @@ -93,10 +93,13 @@ func (c *EventChannel) flush() { return } + // reset buffers and writers + defer c.reset() + // finish writing gzip header - err := c.gz.Flush() + err := c.gz.Close() if err != nil { - glog.Warning("[pubstack] fail to flush gzipped buffer") + glog.Warning("[pubstack] fail to close gzipped buffer") return } @@ -108,9 +111,6 @@ func (c *EventChannel) flush() { return } - // reset buffers and writers - c.reset() - // send events (async) go c.send(payload) } diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go index 9fdcfe976a6..792e15e151e 100644 --- a/analytics/pubstack/eventchannel/eventchannel_test.go +++ b/analytics/pubstack/eventchannel/eventchannel_test.go @@ -134,3 +134,42 @@ func TestEventChannel_Push(t *testing.T) { assert.Equal(t, string(data), "onetwothreefourfivesixseven") } + +func TestEventChannel_OutputFormat(t *testing.T) { + + toGzip := func(payload string) []byte { + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + + if _, err := zw.Write([]byte(payload)); err != nil { + assert.Fail(t, err.Error()) + } + + if err := zw.Close(); err != nil { + assert.Fail(t, err.Error()) + } + return buf.Bytes() + } + + data := make([]byte, 0) + send := func(payload []byte) error { + data = append(data, payload...) + return nil + } + + eventChannel := NewEventChannel(send, 15000, 10, 2*time.Minute) + + eventChannel.Push([]byte("one")) + eventChannel.flush() + eventChannel.Push([]byte("two")) + eventChannel.Push([]byte("three")) + + eventChannel.Close() + + time.Sleep(10 * time.Millisecond) + + expected := append(toGzip("one"), toGzip("twothree")...) + + assert.Equal(t, expected, data) + +} From d8dc27f245d603af9b3f58fb5897b61e417b2d3c Mon Sep 17 00:00:00 2001 From: Stephan Brosinski Date: Tue, 1 Sep 2020 01:02:15 +0200 Subject: [PATCH 184/603] Smaato adapter: support for video mediaType (#1463) Co-authored-by: vikram --- adapters/smaato/smaato.go | 59 ++++-- .../smaato/smaatotest/exemplary/video.json | 187 ++++++++++++++++++ .../supplemental/bad-adm-response.json | 8 +- static/bidder-info/smaato.yaml | 4 +- 4 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/video.json diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 06678d77a61..b48851bb4d2 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -20,6 +20,7 @@ type adMarkupType string const ( smtAdTypeImg adMarkupType = "Img" smtAdTypeRichmedia adMarkupType = "Richmedia" + smtAdTypeVideo adMarkupType = "Video" ) // SmaatoAdapter describes a Smaato prebid server adapter. @@ -63,7 +64,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId - publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") + publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") if err != nil { errs = append(errs, err) return nil, errs @@ -80,7 +81,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } if request.Site != nil { siteCopy := *request.Site - siteCopy.Publisher = &openrtb.Publisher{ID: publisherId} + siteCopy.Publisher = &openrtb.Publisher{ID: publisherID} if request.Site.Ext != nil { var siteExt siteExt @@ -178,16 +179,25 @@ func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] + markupType, markupTypeErr := getAdMarkupType(response, bid.AdM) + if markupTypeErr != nil { + return nil, []error{markupTypeErr} + } + var markupError error - bid.AdM, markupError = renderAdMarkup(getAdMarkupType(response, bid.AdM), bid.AdM) + bid.AdM, markupError = renderAdMarkup(markupType, bid.AdM) if markupError != nil { - fmt.Println(markupError) - continue // no bid when broken ad markup + return nil, []error{markupError} + } + + bidType, bidTypeErr := markupTypeToBidType(markupType) + if bidTypeErr != nil { + return nil, []error{bidTypeErr} } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, + BidType: bidType, }) } } @@ -202,23 +212,41 @@ func renderAdMarkup(adMarkupType adMarkupType, adMarkup string) (string, error) adm, markupError = extractAdmImage(adMarkup) case smtAdTypeRichmedia: adm, markupError = extractAdmRichMedia(adMarkup) + case smtAdTypeVideo: + adm, markupError = adMarkup, nil default: return "", fmt.Errorf("Unknown markup type %s", adMarkupType) } return adm, markupError } -func getAdMarkupType(response *adapters.ResponseData, adMarkup string) adMarkupType { +func markupTypeToBidType(markupType adMarkupType) (openrtb_ext.BidType, error) { + switch markupType { + case smtAdTypeImg: + return openrtb_ext.BidTypeBanner, nil + case smtAdTypeRichmedia: + return openrtb_ext.BidTypeBanner, nil + case smtAdTypeVideo: + return openrtb_ext.BidTypeVideo, nil + default: + return "", fmt.Errorf("Invalid markupType %s", markupType) + } +} + +func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) { if admType := adMarkupType(response.Headers.Get("X-SMT-ADTYPE")); admType != "" { - return admType + return admType, nil } if strings.HasPrefix(adMarkup, `{"image":`) { - return smtAdTypeImg + return smtAdTypeImg, nil } if strings.HasPrefix(adMarkup, `{"richmedia":`) { - return smtAdTypeRichmedia + return smtAdTypeRichmedia, nil + } + if strings.HasPrefix(adMarkup, `", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index 6d4990e9ea4..1fce58f0dfe 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -162,5 +162,11 @@ } } ], - "expectedBidResponses": [] + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"

\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index 662603febdb..db3e61e5cc6 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -4,6 +4,8 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - - banner \ No newline at end of file + - banner + - video From 412e0fcf4fa43f07a3aec843373e5c093093f43b Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Tue, 1 Sep 2020 19:02:39 +0300 Subject: [PATCH 185/603] Rubicon liveramp param (#1466) Add liveramp mapping to user.ext should translate the "liveramp.com" id from the "user.ext.eids" array to "user.ext.liveramp_idl" as follows: ``` { "user": { "ext": { "eids": [{ "source": 'liveramp.com', "uids": [{ "id": "T7JiRRvsRAmh88" }] }] } } } ``` to XAPI: ``` { "user": { "ext": { "liveramp_idl": "T7JiRRvsRAmh88" } } } ``` --- adapters/rubicon/rubicon.go | 60 ++++++++++++++++++++++---------- adapters/rubicon/rubicon_test.go | 14 +++++++- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index a69530de831..56ae7b2f792 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -93,11 +93,12 @@ type rubiconExtUserTpID struct { } type rubiconUserExt struct { - Consent string `json:"consent,omitempty"` - DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` - Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` - TpID []rubiconExtUserTpID `json:"tpid,omitempty"` - RP rubiconUserExtRP `json:"rp"` + Consent string `json:"consent,omitempty"` + DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + TpID []rubiconExtUserTpID `json:"tpid,omitempty"` + RP rubiconUserExtRP `json:"rp"` + LiverampIdl string `json:"liveramp_idl,omitempty"` } type rubiconSiteExtRP struct { @@ -247,6 +248,12 @@ type rubiconUserExtEidUidExt struct { RtiPartner string `json:"rtiPartner,omitempty"` } +type mappedRubiconUidsParam struct { + tpIds []rubiconExtUserTpID + segments []string + liverampIdl string +} + //MAS algorithm func findPrimary(alt []int) (int, []int) { min, pos, primary := 0, 0, 0 @@ -683,13 +690,18 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap // set user.ext.tpid if len(userExt.Eids) > 0 { - if tpIds, segments, errors := getTpIdsAndSegments(userExt.Eids); len(errors) > 0 { + mappedRubiconUidsParam, errors := getTpIdsAndSegments(userExt.Eids) + if len(errors) > 0 { errs = append(errs, errors...) continue - } else if err := updateUserExtWithTpIdsAndSegments(&userExtRP, tpIds, segments); err != nil { + } + + if err := updateUserExtWithTpIdsAndSegments(&userExtRP, mappedRubiconUidsParam); err != nil { errs = append(errs, err) continue } + + userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl } } @@ -793,9 +805,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return requestData, errs } -func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, []string, []error) { - tpIds := make([]rubiconExtUserTpID, 0) - segments := make([]string, 0) +func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { + rubiconUidsParam := mappedRubiconUidsParam{ + tpIds: make([]rubiconExtUserTpID, 0), + segments: make([]string, 0), + } errs := make([]error, 0) for _, eid := range eids { @@ -815,7 +829,7 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [ } if eidUidExt.RtiPartner == "TDID" { - tpIds = append(tpIds, rubiconExtUserTpID{Source: "tdid", UID: uid.ID}) + rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "tdid", UID: uid.ID}) } } } @@ -824,7 +838,7 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [ if len(uids) > 0 { uidId := uids[0].ID if uidId != "" { - tpIds = append(tpIds, rubiconExtUserTpID{Source: "liveintent.com", UID: uidId}) + rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "liveintent.com", UID: uidId}) } if eid.Ext != nil { @@ -835,20 +849,28 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [ }) continue } - segments = eidExt.Segments + rubiconUidsParam.segments = eidExt.Segments + } + } + case "liveramp.com": + uids := eid.Uids + if len(uids) > 0 { + uidId := uids[0].ID + if uidId != "" && rubiconUidsParam.liverampIdl == "" { + rubiconUidsParam.liverampIdl = uidId } } } } - return tpIds, segments, errs + return rubiconUidsParam, errs } -func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, tpIds []rubiconExtUserTpID, segments []string) error { - if len(tpIds) > 0 { - userExtRP.TpID = tpIds +func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error { + if len(rubiconUidsParam.tpIds) > 0 { + userExtRP.TpID = rubiconUidsParam.tpIds - if segments != nil { + if rubiconUidsParam.segments != nil { userExtRPTarget := make(map[string]interface{}) if userExtRP.RP.Target != nil { @@ -857,7 +879,7 @@ func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, tpIds []rubico } } - userExtRPTarget["LIseg"] = segments + userExtRPTarget["LIseg"] = rubiconUidsParam.segments if target, err := json.Marshal(&userExtRPTarget); err != nil { return &errortypes.BadInput{Message: err.Error()} diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 0489797561b..5ec78ccf4f3 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1219,6 +1219,15 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "ext": { "segments": ["999","888"] } + }, + { + "source": "liveramp.com", + "uids": [{ + "id": "LIVERAMPID" + }], + "ext": { + "segments": ["111","222"] + } } ]}`), }, @@ -1239,7 +1248,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { } assert.NotNil(t, userExt.Eids) - assert.Equal(t, 3, len(userExt.Eids), "Eids values are not as expected!") + assert.Equal(t, 4, len(userExt.Eids), "Eids values are not as expected!") assert.NotNil(t, userExt.TpID) assert.Equal(t, 2, len(userExt.TpID), "TpID values are not as expected!") @@ -1250,6 +1259,9 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { // liveintent.com assert.Equal(t, "liveintent.com", userExt.TpID[1].Source, "TpID source value is not as expected!") + // liveramp.com + assert.Equal(t, "LIVERAMPID", userExt.LiverampIdl, "Liveramp_idl value is not as expected!") + userExtRPTarget := make(map[string]interface{}) if err := json.Unmarshal(userExt.RP.Target, &userExtRPTarget); err != nil { t.Fatal("Error unmarshalling request.user.ext.rp.target object.") From 754de04fee53656f669bf5cc52251c688f5a36fa Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Tue, 1 Sep 2020 13:09:32 -0700 Subject: [PATCH 186/603] Consolidate StoredRequest configs, add validation for all data types (#1453) --- config/config.go | 51 ++++-- config/config_test.go | 47 ++++- config/stored_requests.go | 253 ++++++++------------------ config/stored_requests_test.go | 84 ++++++++- stored_requests/config/config.go | 97 +++------- stored_requests/config/config_test.go | 108 +++-------- 6 files changed, 284 insertions(+), 356 deletions(-) diff --git a/config/config.go b/config/config.go index c2e7dfd8f3f..e443a48ec1d 100755 --- a/config/config.go +++ b/config/config.go @@ -29,18 +29,19 @@ type Configuration struct { EnableGzip bool `mapstructure:"enable_gzip"` // StatusResponse is the string which will be returned by the /status endpoint when things are OK. // If empty, it will return a 204 with no content. - StatusResponse string `mapstructure:"status_response"` - AuctionTimeouts AuctionTimeouts `mapstructure:"auction_timeouts_ms"` - CacheURL Cache `mapstructure:"cache"` - ExtCacheURL ExternalCache `mapstructure:"external_cache"` - RecaptchaSecret string `mapstructure:"recaptcha_secret"` - HostCookie HostCookie `mapstructure:"host_cookie"` - Metrics Metrics `mapstructure:"metrics"` - DataCache DataCache `mapstructure:"datacache"` - StoredRequests StoredRequests `mapstructure:"stored_requests"` - CategoryMapping StoredRequestsSlim `mapstructure:"category_mapping"` + StatusResponse string `mapstructure:"status_response"` + AuctionTimeouts AuctionTimeouts `mapstructure:"auction_timeouts_ms"` + CacheURL Cache `mapstructure:"cache"` + ExtCacheURL ExternalCache `mapstructure:"external_cache"` + RecaptchaSecret string `mapstructure:"recaptcha_secret"` + HostCookie HostCookie `mapstructure:"host_cookie"` + Metrics Metrics `mapstructure:"metrics"` + DataCache DataCache `mapstructure:"datacache"` + StoredRequests StoredRequests `mapstructure:"stored_requests"` + StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` + CategoryMapping StoredRequests `mapstructure:"category_mapping"` // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. - StoredVideo StoredRequestsSlim `mapstructure:"stored_video_req"` + StoredVideo StoredRequests `mapstructure:"stored_video_req"` // Adapters should have a key for every openrtb_ext.BidderName, converted to lower-case. // Se also: https://github.com/spf13/viper/issues/371#issuecomment-335388559 @@ -103,7 +104,10 @@ func (c configErrors) Error() string { func (cfg *Configuration) validate() configErrors { var errs configErrors errs = cfg.AuctionTimeouts.validate(errs) - errs = cfg.StoredRequests.validate(errs) + errs = cfg.StoredRequests.validate("stored_req", errs) + errs = cfg.StoredRequestsAMP.validate("stored_amp_req", errs) + errs = cfg.CategoryMapping.validate("categories", errs) + errs = cfg.StoredVideo.validate("stored_video_req", errs) errs = cfg.Metrics.validate(errs) if cfg.MaxRequestSize < 0 { errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize)) @@ -604,6 +608,9 @@ func New(v *viper.Viper) (*Configuration, error) { c.BlacklistedAcctMap[c.BlacklistedAccts[i]] = true } + // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. + resolvedStoredRequestsConfig(&c) + glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") if errs := c.validate(); len(errs) > 0 { @@ -721,6 +728,7 @@ func SetupViper(v *viper.Viper, filename string) { v.AddConfigPath(".") v.AddConfigPath("/etc/config") } + // Fixes #475: Some defaults will be set just so they are accessible via environment variables // (basically so viper knows they exist) v.SetDefault("external_url", "http://localhost:8000") @@ -779,7 +787,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") - v.SetDefault("stored_requests.filesystem", false) + v.SetDefault("stored_requests.filesystem.enabled", false) + v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.postgres.connection.dbname", "") v.SetDefault("stored_requests.postgres.connection.host", "") @@ -995,6 +1004,22 @@ func SetupViper(v *viper.Viper, filename string) { v.SetEnvPrefix("PBS") v.AutomaticEnv() v.ReadInConfig() + + // Migrate config settings to maintain compatibility with old configs + migrateConfig(v) +} + +func migrateConfig(v *viper.Viper) { + // if stored_requests.filesystem is not a map in conf file as expected from defaults, + // means we have old-style settings; migrate them to new filesystem map to avoid breaking viper + if _, ok := v.Get("stored_requests.filesystem").(map[string]interface{}); !ok { + glog.Warning("stored_requests.filesystem should be changed to stored_requests.filesystem.enabled") + glog.Warning("stored_requests.directorypath should be changed to stored_requests.filesystem.directorypath") + m := v.GetStringMap("stored_requests.filesystem") + m["enabled"] = v.GetBool("stored_requests.filesystem") + m["directorypath"] = v.GetString("stored_requests.directorypath") + v.Set("stored_requests.filesystem", m) + } } func setBidderDefaults(v *viper.Viper, bidder string) { diff --git a/config/config_test.go b/config/config_test.go index 40589ac7b23..b23ddd6f614 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "bytes" "net" + "os" "strings" "testing" "time" @@ -135,6 +136,8 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") + cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) + cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) } var fullConfig = []byte(` @@ -287,6 +290,12 @@ adapters: usersync_url: http:\\tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= `) +var oldStoredRequestsConfig = []byte(` +stored_requests: + filesystem: true + directorypath: "/somepath" +`) + func cmpStrings(t *testing.T, key string, a string, b string) { t.Helper() assert.Equal(t, a, b, "%s: %s != %s", key, a, b) @@ -440,17 +449,53 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { func TestValidConfig(t *testing.T) { cfg := Configuration{ StoredRequests: StoredRequests{ - Files: true, + Files: FileFetcherConfig{Enabled: true}, + InMemoryCache: InMemoryCache{ + Type: "none", + }, + }, + StoredVideo: StoredRequests{ + Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ Type: "none", }, }, + CategoryMapping: StoredRequests{ + Files: FileFetcherConfig{Enabled: true}, + }, } + resolvedStoredRequestsConfig(&cfg) err := cfg.validate() assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } +func TestMigrateConfig(t *testing.T) { + v := viper.New() + SetupViper(v, "") + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) + migrateConfig(v) + cfg, err := New(v) + assert.NoError(t, err, "Setting up config should work but it doesn't") + cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) + cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path) +} + +func TestMigrateConfigFromEnv(t *testing.T) { + if oldval, ok := os.LookupEnv("PBS_STORED_REQUESTS_FILESYSTEM"); ok { + defer os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", oldval) + } else { + defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") + } + os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") + v := viper.New() + SetupViper(v, "") + cfg, err := New(v) + assert.NoError(t, err, "Setting up config should work but it doesn't") + cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) +} + func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") diff --git a/config/stored_requests.go b/config/stored_requests.go index 04e400f9b7c..b63073fede7 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -2,7 +2,6 @@ package config import ( "bytes" - "errors" "fmt" "strconv" "strings" @@ -11,44 +10,35 @@ import ( "github.com/golang/glog" ) -// StoredRequests configures the backend used to store requests on the server. -type StoredRequests struct { - // Files should be true if Stored Requests should be loaded from the filesystem. - Files bool `mapstructure:"filesystem"` - //If data should be loaded from file system, path should be specified in configuration - Path string `mapstructure:"directorypath"` - // Postgres configures Fetchers and EventProducers which read from a Postgres DB. - // Fetchers are in stored_requests/backends/db_fetcher/postgres.go - // EventProducers are in stored_requests/events/postgres - Postgres PostgresConfig `mapstructure:"postgres"` - // HTTP configures an instance of stored_requests/backends/http/http_fetcher.go. - // If non-nil, Stored Requests will be fetched from the endpoint described there. - HTTP HTTPFetcherConfig `mapstructure:"http"` - // InMemoryCache configures an instance of stored_requests/caches/memory/cache.go. - // If non-nil, Stored Requests will be saved in an in-memory cache. - InMemoryCache InMemoryCache `mapstructure:"in_memory_cache"` - // CacheEventsAPI configures an instance of stored_requests/events/api/api.go. - // If non-nil, Stored Request Caches can be updated or invalidated through API endpoints. - // This is intended to be a useful development tool and not recommended for a production environment. - // It should not be exposed to public networks without authentication. - CacheEventsAPI bool `mapstructure:"cache_events_api"` - // HTTPEvents configures an instance of stored_requests/events/http/http.go. - // If non-nil, the server will use those endpoints to populate and update the cache. - HTTPEvents HTTPEventsConfig `mapstructure:"http_events"` +// DataType constants +type DataType string + +const ( + RequestDataType DataType = "Request" + CategoryDataType DataType = "Category" + VideoDataType DataType = "Video" + AMPRequestDataType DataType = "AMP Request" +) + +func (sr *StoredRequests) DataType() DataType { + return sr.dataType } -// StoredRequestsSlim struct defines options for stored requests from a single endpoint -type StoredRequestsSlim struct { +// StoredRequests struct defines options for stored requests for each data type +// including some amp stored_requests options +type StoredRequests struct { + // dataType is a tag pushed from upstream indicating the type of object fetched here + dataType DataType // Files should be used if Stored Requests should be loaded from the filesystem. // Fetchers are in stored_requests/backends/file_system/fetcher.go Files FileFetcherConfig `mapstructure:"filesystem"` // Postgres configures Fetchers and EventProducers which read from a Postgres DB. // Fetchers are in stored_requests/backends/db_fetcher/postgres.go // EventProducers are in stored_requests/events/postgres - Postgres PostgresConfigSlim `mapstructure:"postgres"` + Postgres PostgresConfig `mapstructure:"postgres"` // HTTP configures an instance of stored_requests/backends/http/http_fetcher.go. // If non-nil, Stored Requests will be fetched from the endpoint described there. - HTTP HTTPFetcherConfigSlim `mapstructure:"http"` + HTTP HTTPFetcherConfig `mapstructure:"http"` // InMemoryCache configures an instance of stored_requests/caches/memory/cache.go. // If non-nil, Stored Requests will be saved in an in-memory cache. InMemoryCache InMemoryCache `mapstructure:"in_memory_cache"` @@ -57,14 +47,7 @@ type StoredRequestsSlim struct { CacheEvents CacheEventsConfig `mapstructure:"cache_events"` // HTTPEvents configures an instance of stored_requests/events/http/http.go. // If non-nil, the server will use those endpoints to populate and update the cache. - HTTPEvents HTTPEventsConfigSlim `mapstructure:"http_events"` -} - -// HTTPEventsConfigSlim configures stored_requests/events/http/http.go -type HTTPEventsConfigSlim struct { - Endpoint string `mapstructure:"endpoint"` - RefreshRate int64 `mapstructure:"refresh_rate_seconds"` - Timeout int `mapstructure:"timeout_ms"` + HTTPEvents HTTPEventsConfig `mapstructure:"http_events"` } // HTTPEventsConfig configures stored_requests/events/http/http.go @@ -83,14 +66,6 @@ func (cfg HTTPEventsConfig) RefreshRateDuration() time.Duration { return time.Duration(cfg.RefreshRate) * time.Second } -func (cfg HTTPEventsConfigSlim) TimeoutDuration() time.Duration { - return time.Duration(cfg.Timeout) * time.Millisecond -} - -func (cfg HTTPEventsConfigSlim) RefreshRateDuration() time.Duration { - return time.Duration(cfg.RefreshRate) * time.Second -} - // CacheEventsConfig configured stored_requests/events/api/api.go type CacheEventsConfig struct { // Enabled should be true to enable the events api endpoint @@ -107,48 +82,64 @@ type FileFetcherConfig struct { Path string `mapstructure:"directorypath"` } -// HTTPFetcherConfigSlim configures a stored_requests/backends/http_fetcher/fetcher.go -type HTTPFetcherConfigSlim struct { - Endpoint string `mapstructure:"endpoint"` -} - // HTTPFetcherConfig configures a stored_requests/backends/http_fetcher/fetcher.go type HTTPFetcherConfig struct { Endpoint string `mapstructure:"endpoint"` AmpEndpoint string `mapstructure:"amp_endpoint"` } -func (cfg *StoredRequests) validate(errs configErrors) configErrors { +// Migrate combined stored_requests+amp configuration to separate simple config sections +func resolvedStoredRequestsConfig(cfg *Configuration) { + sr := &cfg.StoredRequests + amp := &cfg.StoredRequestsAMP + + sr.CacheEvents.Endpoint = "/storedrequests/openrtb2" // why is this here and not SetDefault ? + + // Amp uses the same config but some fields get replaced by Amp* version of similar fields + cfg.StoredRequestsAMP = cfg.StoredRequests + amp.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.AmpQueryTemplate + amp.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.AmpQuery + amp.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.AmpQuery + amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint + amp.CacheEvents.Endpoint = "/storedrequests/amp" + amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint + + // Set data types for each section + cfg.StoredRequests.dataType = RequestDataType + cfg.StoredRequestsAMP.dataType = AMPRequestDataType + cfg.StoredVideo.dataType = VideoDataType + cfg.CategoryMapping.dataType = CategoryDataType + return +} + +func (cfg *StoredRequests) validate(section string, errs configErrors) configErrors { + errs = cfg.Postgres.validate(section, errs) + + // Categories do not use cache so none of the following checks apply + if cfg.dataType == CategoryDataType { + return errs + } + if cfg.InMemoryCache.Type == "none" { - if cfg.CacheEventsAPI { - errs = append(errs, errors.New("stored_requests.cache_events_api must be false if stored_requests.in_memory_cache=none")) + if cfg.CacheEvents.Enabled { + errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", section)) } if cfg.HTTPEvents.RefreshRate != 0 { - errs = append(errs, errors.New("stored_requests.http_events.refresh_rate_seconds must be 0 if stored_requests.in_memory_cache=none")) + errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", section)) } if cfg.Postgres.PollUpdates.Query != "" { - errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.query must be empty if stored_requests.in_memory_cache=none")) + errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", section)) } if cfg.Postgres.CacheInitialization.Query != "" { - errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.query must be empty if stored_requests.in_memory_cache=none")) + errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", section)) } } - errs = cfg.InMemoryCache.validate(errs) - errs = cfg.Postgres.validate(errs) + errs = cfg.InMemoryCache.validate(section, errs) return errs } -// PostgresConfigSlim configures the Stored Request ecosystem to use Postgres. This must include a Fetcher, -// and may optionally include some EventProducers to populate and refresh the caches. -type PostgresConfigSlim struct { - ConnectionInfo PostgresConnection `mapstructure:"connection"` - FetcherQueries PostgresFetcherQueriesSlim `mapstructure:"fetcher"` - CacheInitialization PostgresCacheInitializerSlim `mapstructure:"initialize_caches"` - PollUpdates PostgresUpdatePollingSlim `mapstructure:"poll_for_updates"` -} - // PostgresConfig configures the Stored Request ecosystem to use Postgres. This must include a Fetcher, // and may optionally include some EventProducers to populate and refresh the caches. type PostgresConfig struct { @@ -158,12 +149,12 @@ type PostgresConfig struct { PollUpdates PostgresUpdatePolling `mapstructure:"poll_for_updates"` } -func (cfg *PostgresConfig) validate(errs configErrors) configErrors { +func (cfg *PostgresConfig) validate(section string, errs configErrors) configErrors { if cfg.ConnectionInfo.Database == "" { return errs } - return cfg.PollUpdates.validate(errs) + return cfg.PollUpdates.validate(section, errs) } // PostgresConnection has options which put types to the Postgres Connection string. See: @@ -242,32 +233,6 @@ type PostgresFetcherQueries struct { AmpQueryTemplate string `mapstructure:"amp_query"` } -type PostgresFetcherQueriesSlim struct { - // QueryTemplate is the Postgres Query which can be used to fetch configs from the database. - // It is a Template, rather than a full Query, because a single HTTP request may reference multiple Stored Requests. - // - // In the simplest case, this could be something like: - // SELECT id, requestData, 'request' as type - // FROM stored_requests - // WHERE id in %REQUEST_ID_LIST% - // UNION ALL - // SELECT id, impData, 'imp' as type - // FROM stored_imps - // WHERE id in %IMP_ID_LIST% - // - // The MakeQuery function will transform this query into: - // SELECT id, requestData, 'request' as type - // FROM stored_requests - // WHERE id in ($1) - // UNION ALL - // SELECT id, impData, 'imp' as type - // FROM stored_imps - // WHERE id in ($2, $3, $4, ...) - // - // ... where the number of "$x" args depends on how many IDs are nested within the HTTP request. - QueryTemplate string `mapstructure:"query"` -} - type PostgresCacheInitializer struct { Timeout int `mapstructure:"timeout_ms"` // Query should be something like: @@ -284,69 +249,19 @@ type PostgresCacheInitializer struct { AmpQuery string `mapstructure:"amp_query"` } -type PostgresCacheInitializerSlim struct { - Timeout int `mapstructure:"timeout_ms"` - // Query should be something like: - // - // SELECT id, requestData, 'request' AS type FROM stored_requests - // UNION ALL - // SELECT id, impData, 'imp' AS type FROM stored_imps - // - // This query will be run once on startup to fetch _all_ known Stored Request data from the database. - // - // For more details on the expected format of requestData and impData, see stored_requests/events/postgres/polling.go - Query string `mapstructure:"query"` -} - -func (cfg *PostgresCacheInitializerSlim) validate(errs configErrors) configErrors { +func (cfg *PostgresCacheInitializer) validate(section string, errs configErrors) configErrors { if cfg.Query == "" { return errs } if cfg.Timeout <= 0 { - errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.timeout_ms must be positive")) + errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.timeout_ms must be positive", section)) } if strings.Contains(cfg.Query, "$") { - errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.query should not contain any wildcards (e.g. $1)")) - } - return errs -} - -func (cfg *PostgresCacheInitializer) validate(errs configErrors) configErrors { - if cfg.Query == "" && cfg.AmpQuery == "" { - return errs - } - - slim := &PostgresCacheInitializerSlim{Timeout: cfg.Timeout, Query: cfg.Query} - errs = slim.validate(errs) - - if strings.Contains(cfg.AmpQuery, "$") { - errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.amp_query should not contain any wildcards (e.g. $1)")) + errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query should not contain any wildcards (e.g. $1)", section)) } - return errs } -type PostgresUpdatePollingSlim struct { - // RefreshRate determines how frequently the Query and AmpQuery are run. - RefreshRate int `mapstructure:"refresh_rate_seconds"` - - // Timeout is the amount of time before a call to the database is aborted. - Timeout int `mapstructure:"timeout_ms"` - - // An example UpdateQuery is: - // - // SELECT id, requestData, 'request' AS type - // FROM stored_requests - // WHERE last_updated > $1 - // UNION ALL - // SELECT id, requestData, 'imp' AS type - // FROM stored_imps - // WHERE last_updated > $1 - // - // The code will be run periodically to fetch updates from the database. - Query string `mapstructure:"query"` -} - type PostgresUpdatePolling struct { // RefreshRate determines how frequently the Query and AmpQuery are run. RefreshRate int `mapstructure:"refresh_rate_seconds"` @@ -370,49 +285,31 @@ type PostgresUpdatePolling struct { AmpQuery string `mapstructure:"amp_query"` } -func (cfg *PostgresUpdatePollingSlim) validate(errs configErrors) configErrors { +func (cfg *PostgresUpdatePolling) validate(section string, errs configErrors) configErrors { if cfg.Query == "" { return errs } if cfg.RefreshRate <= 0 { - errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.refresh_rate_seconds must be > 0")) + errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.refresh_rate_seconds must be > 0", section)) } if cfg.Timeout <= 0 { - errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.timeout_ms must be > 0")) + errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.timeout_ms must be > 0", section)) } if !strings.Contains(cfg.Query, "$1") || strings.Contains(cfg.Query, "$2") { - errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.query must contain exactly one wildcard")) - } - return errs -} - -func (cfg *PostgresUpdatePolling) validate(errs configErrors) configErrors { - if cfg.Query == "" && cfg.AmpQuery == "" { - return errs - } - slim := &PostgresUpdatePollingSlim{RefreshRate: cfg.RefreshRate, Timeout: cfg.Timeout, Query: cfg.Query} - errs = slim.validate(errs) - - if !strings.Contains(cfg.AmpQuery, "$1") || strings.Contains(cfg.AmpQuery, "$2") { - errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.amp_query must contain exactly one wildcard")) + errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must contain exactly one wildcard", section)) } return errs } // MakeQuery builds a query which can fetch numReqs Stored Requests and numImps Stored Imps. // See the docs on PostgresConfig.QueryTemplate for a description of how it works. -func (cfg *PostgresFetcherQueriesSlim) MakeQuery(numReqs int, numImps int) (query string) { +func (cfg *PostgresFetcherQueries) MakeQuery(numReqs int, numImps int) (query string) { return resolve(cfg.QueryTemplate, numReqs, numImps) } -// MakeAmpQuery is the equivalent of MakeQuery() for AMP. -func (cfg *PostgresFetcherQueries) MakeAmpQuery(numReqs int, numImps int) string { - return resolve(cfg.AmpQueryTemplate, numReqs, numImps) -} - func resolve(template string, numReqs int, numImps int) (query string) { numReqs = ensureNonNegative("Request", numReqs) numImps = ensureNonNegative("Imp", numImps) @@ -473,29 +370,29 @@ type InMemoryCache struct { ImpCacheSize int `mapstructure:"imp_cache_size_bytes"` } -func (cfg *InMemoryCache) validate(errs configErrors) configErrors { +func (cfg *InMemoryCache) validate(section string, errs configErrors) configErrors { switch cfg.Type { case "none": // No errors for no config options case "unbounded": if cfg.TTL != 0 { - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache must be 0 for unbounded caches. Got %d", cfg.TTL)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache must be 0 for unbounded caches. Got %d", section, cfg.TTL)) } if cfg.RequestCacheSize != 0 { - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.request_cache_size_bytes must be 0 for unbounded caches. Got %d", cfg.RequestCacheSize)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes must be 0 for unbounded caches. Got %d", section, cfg.RequestCacheSize)) } if cfg.ImpCacheSize != 0 { - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.imp_cache_size_bytes must be 0 for unbounded caches. Got %d", cfg.ImpCacheSize)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes must be 0 for unbounded caches. Got %d", section, cfg.ImpCacheSize)) } case "lru": if cfg.RequestCacheSize <= 0 { - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.request_cache_size_bytes must be >= 0 when stored_requests.in_memory_cache.type=lru. Got %d", cfg.RequestCacheSize)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.RequestCacheSize)) } if cfg.ImpCacheSize <= 0 { - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.imp_cache_size_bytes must be >= 0 when stored_requests.in_memory_cache.type=lru. Got %d", cfg.ImpCacheSize)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.ImpCacheSize)) } default: - errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.type %s is invalid", cfg.Type)) + errs = append(errs, fmt.Errorf("%s: in_memory_cache.type %s is invalid", section, cfg.Type)) } return errs } diff --git a/config/stored_requests_test.go b/config/stored_requests_test.go index 3e01dd3c853..36a5e3793ed 100644 --- a/config/stored_requests_test.go +++ b/config/stored_requests_test.go @@ -78,40 +78,40 @@ func TestPostgressConnString(t *testing.T) { func TestInMemoryCacheValidation(t *testing.T) { assertNoErrs(t, (&InMemoryCache{ Type: "unbounded", - }).validate(nil)) + }).validate("Test", nil)) assertNoErrs(t, (&InMemoryCache{ Type: "none", - }).validate(nil)) + }).validate("Test", nil)) assertNoErrs(t, (&InMemoryCache{ Type: "lru", RequestCacheSize: 1000, ImpCacheSize: 1000, - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "unrecognized", - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "unbounded", ImpCacheSize: 1000, - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "unbounded", RequestCacheSize: 1000, - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "unbounded", TTL: 500, - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "lru", RequestCacheSize: 0, ImpCacheSize: 1000, - }).validate(nil)) + }).validate("Test", nil)) assertErrsExist(t, (&InMemoryCache{ Type: "lru", RequestCacheSize: 1000, ImpCacheSize: 0, - }).validate(nil)) + }).validate("Test", nil)) } func assertErrsExist(t *testing.T, err configErrors) { @@ -140,7 +140,7 @@ func assertHasValue(t *testing.T, m map[string]string, key string, val string) { } func buildQuery(template string, numReqs int, numImps int) string { - cfg := PostgresFetcherQueriesSlim{} + cfg := PostgresFetcherQueries{} cfg.QueryTemplate = template return cfg.MakeQuery(numReqs, numImps) @@ -152,3 +152,67 @@ func assertStringsEqual(t *testing.T, actual string, expected string) { } } + +func TestResolveConfig(t *testing.T) { + cfg := &Configuration{ + StoredRequests: StoredRequests{ + Files: FileFetcherConfig{ + Enabled: true, + Path: "/test-path"}, + Postgres: PostgresConfig{ + ConnectionInfo: PostgresConnection{ + Database: "db", + Host: "pghost", + Port: 5, + Username: "user", + Password: "pass", + }, + FetcherQueries: PostgresFetcherQueries{ + AmpQueryTemplate: "amp-fetcher-query", + }, + CacheInitialization: PostgresCacheInitializer{ + AmpQuery: "amp-cache-init-query", + }, + PollUpdates: PostgresUpdatePolling{ + AmpQuery: "amp-poll-query", + }, + }, + HTTP: HTTPFetcherConfig{ + AmpEndpoint: "amp-http-fetcher-endpoint", + }, + InMemoryCache: InMemoryCache{ + Type: "none", + TTL: 50, + RequestCacheSize: 1, + ImpCacheSize: 2, + }, + CacheEvents: CacheEventsConfig{ + Enabled: true, + }, + HTTPEvents: HTTPEventsConfig{ + AmpEndpoint: "amp-http-events-endpoint", + }, + }, + } + + cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate = "auc-fetcher-query" + cfg.StoredRequests.Postgres.CacheInitialization.Query = "auc-cache-init-query" + cfg.StoredRequests.Postgres.PollUpdates.Query = "auc-poll-query" + cfg.StoredRequests.HTTP.Endpoint = "auc-http-fetcher-endpoint" + cfg.StoredRequests.HTTPEvents.Endpoint = "auc-http-events-endpoint" + + resolvedStoredRequestsConfig(cfg) + auc := &cfg.StoredRequests + amp := &cfg.StoredRequestsAMP + + // Auction should have the non-amp values in it + assertStringsEqual(t, auc.CacheEvents.Endpoint, "/storedrequests/openrtb2") + + // Amp should have the amp values in it + assertStringsEqual(t, amp.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.AmpQueryTemplate) + assertStringsEqual(t, amp.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.AmpQuery) + assertStringsEqual(t, amp.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.AmpQuery) + assertStringsEqual(t, amp.HTTP.Endpoint, cfg.StoredRequests.HTTP.AmpEndpoint) + assertStringsEqual(t, amp.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.AmpEndpoint) + assertStringsEqual(t, amp.CacheEvents.Endpoint, "/storedrequests/amp") +} diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index e8f6b0bdf46..8f06efcb32b 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -41,25 +41,26 @@ type dbConnection struct { // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func CreateStoredRequests(cfg *config.StoredRequestsSlim, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router, dbc *dbConnection) (fetcher stored_requests.AllFetcher, shutdown func()) { +func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router, dbc *dbConnection) (fetcher stored_requests.AllFetcher, shutdown func()) { // Create database connection if given options for one if cfg.Postgres.ConnectionInfo.Database != "" { conn := cfg.Postgres.ConnectionInfo.ConnString() if dbc.conn == "" { - glog.Infof("Connecting to Postgres for Stored Requests. DB=%s, host=%s, port=%d, user=%s", + glog.Infof("Connecting to Postgres for Stored %s. DB=%s, host=%s, port=%d, user=%s", + cfg.DataType(), cfg.Postgres.ConnectionInfo.Database, cfg.Postgres.ConnectionInfo.Host, cfg.Postgres.ConnectionInfo.Port, cfg.Postgres.ConnectionInfo.Username) - db := newPostgresDB(cfg.Postgres.ConnectionInfo) + db := newPostgresDB(cfg.DataType(), cfg.Postgres.ConnectionInfo) dbc.conn = conn dbc.db = db } // Error out if config is trying to use multiple database connections for different stored requests (not supported yet) if conn != dbc.conn { - glog.Fatal("Multiple database connection settings found in Stored Requests config, only a single database connection is currently supported.") + glog.Fatal("Multiple database connection settings found in config, only a single database connection is currently supported.") } } @@ -106,9 +107,6 @@ func CreateStoredRequests(cfg *config.StoredRequestsSlim, metricsEngine pbsmetri // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { - // Build individual slim options from combined config struct - slimAuction, slimAmp := resolvedStoredRequestsConfig(cfg) - // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -116,8 +114,8 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri var dbc dbConnection - fetcher1, shutdown1 := CreateStoredRequests(&slimAuction, metricsEngine, client, router, &dbc) - fetcher2, shutdown2 := CreateStoredRequests(&slimAmp, metricsEngine, client, router, &dbc) + fetcher1, shutdown1 := CreateStoredRequests(&cfg.StoredRequests, metricsEngine, client, router, &dbc) + fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, &dbc) fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, &dbc) fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) @@ -138,48 +136,6 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri return } -func resolvedStoredRequestsConfig(cfg *config.Configuration) (auc, amp config.StoredRequestsSlim) { - sr := &cfg.StoredRequests - - // Auction endpoint uses non-Amp fields so can just copy the slin data - auc.Files.Enabled = sr.Files - auc.Files.Path = sr.Path - auc.Postgres.ConnectionInfo = sr.Postgres.ConnectionInfo - auc.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.QueryTemplate - auc.Postgres.CacheInitialization.Timeout = sr.Postgres.CacheInitialization.Timeout - auc.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.Query - auc.Postgres.PollUpdates.RefreshRate = sr.Postgres.PollUpdates.RefreshRate - auc.Postgres.PollUpdates.Timeout = sr.Postgres.PollUpdates.Timeout - auc.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.Query - auc.HTTP.Endpoint = sr.HTTP.Endpoint - auc.InMemoryCache = sr.InMemoryCache - auc.CacheEvents.Enabled = sr.CacheEventsAPI - auc.CacheEvents.Endpoint = "/storedrequests/openrtb2" - auc.HTTPEvents.RefreshRate = sr.HTTPEvents.RefreshRate - auc.HTTPEvents.Timeout = sr.HTTPEvents.Timeout - auc.HTTPEvents.Endpoint = sr.HTTPEvents.Endpoint - - // Amp endpoint uses all the slim data but some fields get replacyed by Amp* version of similar fields - amp.Files.Enabled = sr.Files - amp.Files.Path = sr.Path - amp.Postgres.ConnectionInfo = sr.Postgres.ConnectionInfo - amp.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.AmpQueryTemplate - amp.Postgres.CacheInitialization.Timeout = sr.Postgres.CacheInitialization.Timeout - amp.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.AmpQuery - amp.Postgres.PollUpdates.RefreshRate = sr.Postgres.PollUpdates.RefreshRate - amp.Postgres.PollUpdates.Timeout = sr.Postgres.PollUpdates.Timeout - amp.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.AmpQuery - amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint - amp.InMemoryCache = sr.InMemoryCache - amp.CacheEvents.Enabled = sr.CacheEventsAPI - amp.CacheEvents.Endpoint = "/storedrequests/amp" - amp.HTTPEvents.RefreshRate = sr.HTTPEvents.RefreshRate - amp.HTTPEvents.Timeout = sr.HTTPEvents.Timeout - amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint - - return -} - func addListeners(cache stored_requests.Cache, eventProducers []events.EventProducer) (shutdown func()) { listeners := make([]*events.EventListener, 0, len(eventProducers)) @@ -196,36 +152,36 @@ func addListeners(cache stored_requests.Cache, eventProducers []events.EventProd } } -func newFetcher(cfg *config.StoredRequestsSlim, client *http.Client, db *sql.DB) (fetcher stored_requests.AllFetcher) { +func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fetcher stored_requests.AllFetcher) { idList := make(stored_requests.MultiFetcher, 0, 3) if cfg.Files.Enabled { - fFetcher := newFilesystem(cfg.Files.Path) + fFetcher := newFilesystem(cfg.DataType(), cfg.Files.Path) idList = append(idList, fFetcher) } if cfg.Postgres.FetcherQueries.QueryTemplate != "" { - glog.Infof("Loading Stored Requests via Postgres.\nQuery: %s", cfg.Postgres.FetcherQueries.QueryTemplate) + glog.Infof("Loading Stored %s data via Postgres.\nQuery: %s", cfg.DataType(), cfg.Postgres.FetcherQueries.QueryTemplate) idList = append(idList, db_fetcher.NewFetcher(db, cfg.Postgres.FetcherQueries.MakeQuery)) } if cfg.HTTP.Endpoint != "" { - glog.Infof("Loading Stored Requests via HTTP. endpoint=%s", cfg.HTTP.Endpoint) + glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint) idList = append(idList, http_fetcher.NewFetcher(client, cfg.HTTP.Endpoint)) } - fetcher = consolidate(idList) + fetcher = consolidate(cfg.DataType(), idList) return } -func newCache(cfg *config.StoredRequestsSlim) stored_requests.Cache { +func newCache(cfg *config.StoredRequests) stored_requests.Cache { if cfg.InMemoryCache.Type == "none" { - glog.Info("No Stored Request cache configured. The Fetcher backend will be used for all Stored Requests.") + glog.Infof("No Stored %s cache configured. The %s Fetcher backend will be used for all data requests", cfg.DataType(), cfg.DataType()) return &nil_cache.NilCache{} } return memory.NewCache(&cfg.InMemoryCache) } -func newEventProducers(cfg *config.StoredRequestsSlim, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) { +func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) { if cfg.CacheEvents.Enabled { eventProducers = append(eventProducers, newEventsAPI(router, cfg.CacheEvents.Endpoint)) } @@ -247,7 +203,7 @@ func newEventProducers(cfg *config.StoredRequestsSlim, client *http.Client, db * return } -func newPostgresPolling(cfg config.PostgresUpdatePollingSlim, db *sql.DB, startTime time.Time) events.EventProducer { +func newPostgresPolling(cfg config.PostgresUpdatePolling, db *sql.DB, startTime time.Time) events.EventProducer { timeout := time.Duration(cfg.Timeout) * time.Millisecond ctxProducer := func() (ctx context.Context, canceller func()) { return context.WithTimeout(context.Background(), timeout) @@ -269,32 +225,37 @@ func newHttpEvents(client *http.Client, timeout time.Duration, refreshRate time. return httpEvents.NewHTTPEvents(client, endpoint, ctxProducer, refreshRate) } -func newFilesystem(configPath string) stored_requests.AllFetcher { - glog.Infof("Loading Stored Requests from filesystem at path %s", configPath) +func newFilesystem(dataType config.DataType, configPath string) stored_requests.AllFetcher { + glog.Infof("Loading Stored %s data from filesystem at path %s", dataType, configPath) fetcher, err := file_fetcher.NewFileFetcher(configPath) if err != nil { - glog.Fatalf("Failed to create a FileFetcher: %v", err) + glog.Fatalf("Failed to create a %s FileFetcher: %v", dataType, err) } return fetcher } -func newPostgresDB(cfg config.PostgresConnection) *sql.DB { +func newPostgresDB(dataType config.DataType, cfg config.PostgresConnection) *sql.DB { db, err := sql.Open("postgres", cfg.ConnString()) if err != nil { - glog.Fatalf("Failed to open postgres connection: %v", err) + glog.Fatalf("Failed to open %s postgres connection: %v", dataType, err) } if err := db.Ping(); err != nil { - glog.Fatalf("Failed to ping postgres: %v", err) + glog.Fatalf("Failed to ping %s postgres: %v", dataType, err) } return db } // consolidate returns a single Fetcher from an array of fetchers of any size. -func consolidate(fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher { +func consolidate(dataType config.DataType, fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher { if len(fetchers) == 0 { - glog.Warning("No Stored Request support configured. request.imp[i].ext.prebid.storedrequest will be ignored. If you need this, check your app config") + switch dataType { + case config.RequestDataType: + glog.Warning("No Stored Request support configured. request.imp[i].ext.prebid.storedrequest will be ignored. If you need this, check your app config") + default: + glog.Warningf("No Stored %s support configured. If you need this, check your app config", dataType) + } return empty_fetcher.EmptyFetcher{} } else if len(fetchers) == 1 { return fetchers[0] diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index 11748a59966..4c3943ea5be 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -19,8 +19,8 @@ import ( ) func TestNewEmptyFetcher(t *testing.T) { - fetcher := newFetcher(&config.StoredRequestsSlim{}, nil, nil) - ampFetcher := newFetcher(&config.StoredRequestsSlim{}, nil, nil) + fetcher := newFetcher(&config.StoredRequests{}, nil, nil) + ampFetcher := newFetcher(&config.StoredRequests{}, nil, nil) if fetcher == nil || ampFetcher == nil { t.Errorf("The fetchers should be non-nil, even with an empty config.") } @@ -33,13 +33,13 @@ func TestNewEmptyFetcher(t *testing.T) { } func TestNewHTTPFetcher(t *testing.T) { - fetcher := newFetcher(&config.StoredRequestsSlim{ - HTTP: config.HTTPFetcherConfigSlim{ + fetcher := newFetcher(&config.StoredRequests{ + HTTP: config.HTTPFetcherConfig{ Endpoint: "stored-requests.prebid.com", }, }, nil, nil) - ampFetcher := newFetcher(&config.StoredRequestsSlim{ - HTTP: config.HTTPFetcherConfigSlim{ + ampFetcher := newFetcher(&config.StoredRequests{ + HTTP: config.HTTPFetcherConfig{ Endpoint: "stored-requests.prebid.com?type=amp", }, }, nil, nil) @@ -60,13 +60,13 @@ func TestNewHTTPFetcher(t *testing.T) { } func TestNewHTTPFetcherNoAmp(t *testing.T) { - fetcher := newFetcher(&config.StoredRequestsSlim{ - HTTP: config.HTTPFetcherConfigSlim{ + fetcher := newFetcher(&config.StoredRequests{ + HTTP: config.HTTPFetcherConfig{ Endpoint: "stored-requests.prebid.com", }, }, nil, nil) - ampFetcher := newFetcher(&config.StoredRequestsSlim{ - HTTP: config.HTTPFetcherConfigSlim{ + ampFetcher := newFetcher(&config.StoredRequests{ + HTTP: config.HTTPFetcherConfig{ Endpoint: "", }, }, nil, nil) @@ -82,78 +82,14 @@ func TestNewHTTPFetcherNoAmp(t *testing.T) { } } -func TestResolveConfig(t *testing.T) { - cfg := &config.Configuration{ - StoredRequests: config.StoredRequests{ - Files: true, - Path: "/test-path", - Postgres: config.PostgresConfig{ - ConnectionInfo: config.PostgresConnection{ - Database: "db", - Host: "pghost", - Port: 5, - Username: "user", - Password: "pass", - }, - FetcherQueries: config.PostgresFetcherQueries{ - AmpQueryTemplate: "amp-fetcher-query", - }, - CacheInitialization: config.PostgresCacheInitializer{ - AmpQuery: "amp-cache-init-query", - }, - PollUpdates: config.PostgresUpdatePolling{ - AmpQuery: "amp-poll-query", - }, - }, - HTTP: config.HTTPFetcherConfig{ - AmpEndpoint: "amp-http-fetcher-endpoint", - }, - InMemoryCache: config.InMemoryCache{ - Type: "none", - TTL: 50, - RequestCacheSize: 1, - ImpCacheSize: 2, - }, - CacheEventsAPI: true, - HTTPEvents: config.HTTPEventsConfig{ - AmpEndpoint: "amp-http-events-endpoint", - }, - }, - } - - cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate = "auc-fetcher-query" - cfg.StoredRequests.Postgres.CacheInitialization.Query = "auc-cache-init-query" - cfg.StoredRequests.Postgres.PollUpdates.Query = "auc-poll-query" - cfg.StoredRequests.HTTP.Endpoint = "auc-http-fetcher-endpoint" - cfg.StoredRequests.HTTPEvents.Endpoint = "auc-http-events-endpoint" - - auc, amp := resolvedStoredRequestsConfig(cfg) - - // Auction slim should have the non-amp values in it - assertStringsEqual(t, auc.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate) - assertStringsEqual(t, auc.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.Query) - assertStringsEqual(t, auc.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.Query) - assertStringsEqual(t, auc.HTTP.Endpoint, cfg.StoredRequests.HTTP.Endpoint) - assertStringsEqual(t, auc.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.Endpoint) - assertStringsEqual(t, auc.CacheEvents.Endpoint, "/storedrequests/openrtb2") - - // Amp slim should have the amp values in it - assertStringsEqual(t, amp.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.AmpQueryTemplate) - assertStringsEqual(t, amp.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.AmpQuery) - assertStringsEqual(t, amp.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.AmpQuery) - assertStringsEqual(t, amp.HTTP.Endpoint, cfg.StoredRequests.HTTP.AmpEndpoint) - assertStringsEqual(t, amp.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.AmpEndpoint) - assertStringsEqual(t, amp.CacheEvents.Endpoint, "/storedrequests/amp") -} - func TestNewHTTPEvents(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) } server1 := httptest.NewServer(http.HandlerFunc(handler)) - cfg := &config.StoredRequestsSlim{ - HTTPEvents: config.HTTPEventsConfigSlim{ + cfg := &config.StoredRequests{ + HTTPEvents: config.HTTPEventsConfig{ Endpoint: server1.URL, RefreshRate: 100, Timeout: 1000, @@ -165,7 +101,7 @@ func TestNewHTTPEvents(t *testing.T) { } func TestNewEmptyCache(t *testing.T) { - cache := newCache(&config.StoredRequestsSlim{InMemoryCache: config.InMemoryCache{Type: "none"}}) + cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}}) cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil) reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil) if len(reqs) != 0 { @@ -174,7 +110,7 @@ func TestNewEmptyCache(t *testing.T) { } func TestNewInMemoryCache(t *testing.T) { - cache := newCache(&config.StoredRequestsSlim{ + cache := newCache(&config.StoredRequests{ InMemoryCache: config.InMemoryCache{ TTL: 60, RequestCacheSize: 100, @@ -189,26 +125,26 @@ func TestNewInMemoryCache(t *testing.T) { } func TestNewPostgresEventProducers(t *testing.T) { - cfg := &config.StoredRequestsSlim{ - Postgres: config.PostgresConfigSlim{ - CacheInitialization: config.PostgresCacheInitializerSlim{ + cfg := &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ Timeout: 50, Query: "SELECT id, requestData, type FROM stored_data", }, - PollUpdates: config.PostgresUpdatePollingSlim{ + PollUpdates: config.PostgresUpdatePolling{ RefreshRate: 20, Timeout: 50, Query: "SELECT id, requestData, type FROM stored_data WHERE last_updated > $1", }, }, } - ampCfg := &config.StoredRequestsSlim{ - Postgres: config.PostgresConfigSlim{ - CacheInitialization: config.PostgresCacheInitializerSlim{ + ampCfg := &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ Timeout: 50, Query: "SELECT id, requestData, type FROM stored_amp_data", }, - PollUpdates: config.PostgresUpdatePollingSlim{ + PollUpdates: config.PostgresUpdatePolling{ RefreshRate: 20, Timeout: 50, Query: "SELECT id, requestData, type FROM stored_amp_data WHERE last_updated > $1", From f350cdab662a6feca661226fec0cd10f69363ab8 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 2 Sep 2020 12:51:09 -0400 Subject: [PATCH 187/603] Fix Test TestEventChannel_OutputFormat (#1468) --- analytics/pubstack/eventchannel/eventchannel_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go index 792e15e151e..f450fb61fe1 100644 --- a/analytics/pubstack/eventchannel/eventchannel_test.go +++ b/analytics/pubstack/eventchannel/eventchannel_test.go @@ -3,11 +3,12 @@ package eventchannel import ( "bytes" "compress/gzip" - "github.com/stretchr/testify/assert" "io/ioutil" "sync" "testing" "time" + + "github.com/stretchr/testify/assert" ) var maxByteSize = int64(15) @@ -160,16 +161,21 @@ func TestEventChannel_OutputFormat(t *testing.T) { eventChannel := NewEventChannel(send, 15000, 10, 2*time.Minute) eventChannel.Push([]byte("one")) + time.Sleep(1 * time.Millisecond) + eventChannel.flush() + eventChannel.Push([]byte("two")) + time.Sleep(1 * time.Millisecond) + eventChannel.Push([]byte("three")) + time.Sleep(1 * time.Millisecond) eventChannel.Close() - time.Sleep(10 * time.Millisecond) + time.Sleep(1 * time.Millisecond) expected := append(toGzip("one"), toGzip("twothree")...) assert.Equal(t, expected, data) - } From c867e6f7edbd8fbe197b16441f1792a05642973b Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Wed, 2 Sep 2020 16:24:49 -0400 Subject: [PATCH 188/603] Add ability to randomly generate source.TID if empty and set publisher.ID to resolved account ID (#1439) --- config/config.go | 3 + config/config_test.go | 3 + endpoints/openrtb2/amp_auction.go | 35 ++++-- endpoints/openrtb2/amp_auction_test.go | 79 +++++++++++++ endpoints/openrtb2/auction.go | 31 ++++- endpoints/openrtb2/auction_test.go | 158 +++++++++++++++++++++++-- endpoints/openrtb2/video_auction.go | 4 +- exchange/utils.go | 7 +- 8 files changed, 293 insertions(+), 27 deletions(-) diff --git a/config/config.go b/config/config.go index e443a48ec1d..23cc35719db 100755 --- a/config/config.go +++ b/config/config.go @@ -73,6 +73,8 @@ type Configuration struct { Debug Debug `mapstructure:"debug"` // RequestValidation specifies the request validation options. RequestValidation RequestValidation `mapstructure:"request_validation"` + // When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty + AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -975,6 +977,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("blacklisted_accts", []string{""}) v.SetDefault("account_required", false) v.SetDefault("certificates_file", "") + v.SetDefault("auto_gen_source_tid", true) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index b23ddd6f614..23764d216ef 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -138,6 +138,7 @@ func TestDefaults(t *testing.T) { cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) + cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true) } var fullConfig = []byte(` @@ -224,6 +225,7 @@ adapters: usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= blacklisted_apps: ["spamAppID","sketchy-app-id"] account_required: true +auto_gen_source_tid: false certificates_file: /etc/ssl/cert.pem request_validation: ipv4_private_networks: ["1.1.1.0/24"] @@ -406,6 +408,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "adapters.rhythmone.endpoint", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint, "http://tag.1rx.io/rmp") cmpStrings(t, "adapters.rhythmone.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].UserSyncURL, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Fprebid-server.prebid.org%2F%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") cmpBools(t, "account_required", cfg.AccountRequired, true) + cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8efba5a926c..f5b72e029e0 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -154,7 +154,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } else { labels.CookieFlag = pbsmetrics.CookieFlagYes } - labels.PubID = effectivePubID(req.Site.Publisher) + labels.PubID = getAccountID(req.Site.Publisher) + // Blacklist account now that we have resolved the value if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { errL = append(errL, acctIdErr) @@ -388,13 +389,7 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope setAmpExt(req.Site, "1") - account := httpRequest.FormValue("account") - if account != "" { - if req.Site.Publisher == nil { - req.Site.Publisher = &openrtb.Publisher{} - } - req.Site.Publisher.ID = account - } + setEffectiveAmpPubID(req, httpRequest.URL.Query()) slot := httpRequest.FormValue("slot") if slot != "" { @@ -564,3 +559,27 @@ func readConsent(url *url.URL) string { // Fallback to 'gdpr_consent' for compatability until it's no longer used by AMP. return url.Query().Get("gdpr_consent") } + +// Sets the effective publisher ID for amp request +func setEffectiveAmpPubID(req *openrtb.BidRequest, urlQueryParams url.Values) { + var pub *openrtb.Publisher + if req.App != nil { + if req.App.Publisher == nil { + req.App.Publisher = new(openrtb.Publisher) + } + pub = req.App.Publisher + } else if req.Site != nil { + if req.Site.Publisher == nil { + req.Site.Publisher = new(openrtb.Publisher) + } + pub = req.Site.Publisher + } + + if pub.ID == "" { + // For amp requests, the publisher ID could be sent via the account + // query string + if acc := urlQueryParams.Get("account"); acc != "" && acc != "ACCOUNT_ID" { + pub.ID = acc + } + } +} diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 692d3fb0c5d..be6735c4c39 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -1042,3 +1042,82 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, return json.Marshal(bidRequest) } + +func TestSetEffectiveAmpPubID(t *testing.T) { + testPubID := "test-pub" + testURLQueryParams := url.Values{} + testURLQueryParams.Add("account", testPubID) + + testCases := []struct { + description string + req *openrtb.BidRequest + urlQueryParams url.Values + expectedPubID string + }{ + { + description: "No publisher ID provided", + req: &openrtb.BidRequest{ + App: &openrtb.App{ + Publisher: nil, + }, + }, + expectedPubID: "", + }, + { + description: "Publisher ID present in req.App.Publisher.ID", + req: &openrtb.BidRequest{ + App: &openrtb.App{ + Publisher: &openrtb.Publisher{ + ID: testPubID, + }, + }, + }, + expectedPubID: testPubID, + }, + { + description: "Publisher ID present in req.Site.Publisher.ID", + req: &openrtb.BidRequest{ + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: testPubID, + }, + }, + }, + expectedPubID: testPubID, + }, + { + description: "Publisher ID present in account query parameter", + req: &openrtb.BidRequest{ + App: &openrtb.App{ + Publisher: &openrtb.Publisher{ + ID: "", + }, + }, + }, + urlQueryParams: testURLQueryParams, + expectedPubID: testPubID, + }, + { + description: "req.Site.Publisher present but ID set to empty string", + req: &openrtb.BidRequest{ + Site: &openrtb.Site{ + Publisher: &openrtb.Publisher{ + ID: "", + }, + }, + }, + expectedPubID: "", + }, + } + + for _, test := range testCases { + setEffectiveAmpPubID(test.req, test.urlQueryParams) + if test.req.Site != nil { + assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID, + "should return the expected Publisher ID for test case: %s", test.description) + } else { + assert.Equal(t, test.expectedPubID, test.req.App.Publisher.ID, + "should return the expected Publisher ID for test case: %s", test.description) + } + } +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 86186fa8373..bad2f8aae5b 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -15,6 +15,7 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" + "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/mssola/user_agent" @@ -141,7 +142,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http if req.App != nil { labels.Source = pbsmetrics.DemandApp labels.RType = pbsmetrics.ReqTypeORTB2App - labels.PubID = effectivePubID(req.App.Publisher) + labels.PubID = getAccountID(req.App.Publisher) } else { //req.Site != nil labels.Source = pbsmetrics.DemandWeb if usersyncs.LiveSyncCount() == 0 { @@ -149,7 +150,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } else { labels.CookieFlag = pbsmetrics.CookieFlagYes } - labels.PubID = effectivePubID(req.Site.Publisher) + labels.PubID = getAccountID(req.Site.Publisher) } if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { @@ -283,6 +284,14 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("A prebid request can only process one currency. Taking the first currency in the list, %s, as the active currency", req.Cur[0])}) } + // If automatically filling source TID is enabled then validate that + // source.TID exists and If it doesn't, fill it with a randomly generated UUID + if deps.cfg.AutoGenSourceTID { + if err := validateAndFillSourceTID(req); err != nil { + return []error{err} + } + } + var aliases map[string]string if bidExt, err := deps.parseBidExt(req.Ext); err != nil { return []error{err} @@ -358,6 +367,20 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return errL } +func validateAndFillSourceTID(req *openrtb.BidRequest) error { + if req.Source == nil { + req.Source = &openrtb.Source{} + } + if req.Source.TID == "" { + if rawUUID, err := uuid.NewV4(); err == nil { + req.Source.TID = rawUUID.String() + } else { + return errors.New("req.Source.TID missing in the req and error creating a random UID") + } + } + return nil +} + func validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases map[string]string) error { for bidderToAdjust, adjustmentFactor := range adjustmentFactors { if adjustmentFactor <= 0 { @@ -1265,8 +1288,8 @@ func writeError(errs []error, w http.ResponseWriter, labels *pbsmetrics.Labels) return rc } -// Returns the effective publisher ID -func effectivePubID(pub *openrtb.Publisher) string { +// Returns the account ID for the request +func getAccountID(pub *openrtb.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 957760c61c9..6baab8d500f 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1112,16 +1112,6 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs) } -func TestEffectivePubID(t *testing.T) { - var pub openrtb.Publisher - assert.Equal(t, pbsmetrics.PublisherUnknown, effectivePubID(nil), "effectivePubID failed for nil Publisher.") - assert.Equal(t, pbsmetrics.PublisherUnknown, effectivePubID(&pub), "effectivePubID failed for empty Publisher.") - pub.ID = "123" - assert.Equal(t, "123", effectivePubID(&pub), "effectivePubID failed for standard Publisher.") - pub.Ext = json.RawMessage(`{"prebid": {"parentAccount": "abc"} }`) - assert.Equal(t, "abc", effectivePubID(&pub), "effectivePubID failed for parentAccount.") -} - func validRequest(t *testing.T, filename string) string { requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename) if err != nil { @@ -1222,6 +1212,54 @@ func TestCCPAInvalid(t *testing.T) { assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } +func TestValidateSourceTID(t *testing.T) { + cfg := &config.Configuration{ + AutoGenSourceTID: true, + } + + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + cfg, + pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + ui := uint64(1) + req := openrtb.BidRequest{ + ID: "someID", + Imp: []openrtb.Imp{ + { + ID: "imp-ID", + Banner: &openrtb.Banner{ + W: &ui, + H: &ui, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), + }, + }, + Site: &openrtb.Site{ + ID: "myID", + }, + Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`), + }, + } + + deps.validateRequest(&req) + assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") +} + func TestSChainInvalid(t *testing.T) { deps := &endpointDeps{ &nobidExchange{}, @@ -1269,6 +1307,63 @@ func TestSChainInvalid(t *testing.T) { assert.ElementsMatch(t, errL, []error{expectedError}) } +func TestGetAccountID(t *testing.T) { + testPubID := "test-pub" + testParentAccount := "test-account" + testPubExt := openrtb_ext.ExtPublisher{ + Prebid: &openrtb_ext.ExtPublisherPrebid{ + ParentAccount: &testParentAccount, + }, + } + testPubExtJSON, err := json.Marshal(testPubExt) + assert.NoError(t, err) + + testCases := []struct { + description string + pub *openrtb.Publisher + expectedAccID string + }{ + { + description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present", + pub: &openrtb.Publisher{ + ID: testPubID, + Ext: testPubExtJSON, + }, + expectedAccID: testParentAccount, + }, + { + description: "Only Publisher.Ext.Prebid.ParentAccount present", + pub: &openrtb.Publisher{ + ID: "", + Ext: testPubExtJSON, + }, + expectedAccID: testParentAccount, + }, + { + description: "Only Publisher.ID present", + pub: &openrtb.Publisher{ + ID: testPubID, + }, + expectedAccID: testPubID, + }, + { + description: "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present", + pub: &openrtb.Publisher{}, + expectedAccID: pbsmetrics.PublisherUnknown, + }, + { + description: "Publisher is nil", + pub: nil, + expectedAccID: pbsmetrics.PublisherUnknown, + }, + } + + for _, test := range testCases { + acc := getAccountID(test.pub) + assert.Equal(t, test.expectedAccID, acc, "getAccountID should return expected account for test case: %s", test.description) + } +} + func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string @@ -1344,6 +1439,49 @@ func TestSanitizeRequest(t *testing.T) { } } +func TestValidateAndFillSourceTID(t *testing.T) { + testTID := "some-tid" + testCases := []struct { + description string + req *openrtb.BidRequest + expectRandTID bool + expectedTID string + }{ + { + description: "req.Source not present. Expecting a randomly generated TID value", + req: &openrtb.BidRequest{}, + expectRandTID: true, + }, + { + description: "req.Source.TID not present. Expecting a randomly generated TID value", + req: &openrtb.BidRequest{ + Source: &openrtb.Source{}, + }, + expectRandTID: true, + }, + { + description: "req.Source.TID present. Expecting no change", + req: &openrtb.BidRequest{ + Source: &openrtb.Source{ + TID: testTID, + }, + }, + expectRandTID: false, + expectedTID: testTID, + }, + } + + for _, test := range testCases { + _ = validateAndFillSourceTID(test.req) + if test.expectRandTID { + assert.NotEmpty(t, test.req.Source.TID, test.description) + assert.NotEqual(t, test.expectedTID, test.req.Source.TID, test.description) + } else { + assert.Equal(t, test.expectedTID, test.req.Source.TID, test.description) + } + } +} + // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { gotRequest *openrtb.BidRequest diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index a6ca527874a..a8e4c28b167 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -242,7 +242,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re usersyncs := usersync.ParsePBSCookieFromRequest(r, &(deps.cfg.HostCookie)) if bidReq.App != nil { labels.Source = pbsmetrics.DemandApp - labels.PubID = effectivePubID(bidReq.App.Publisher) + labels.PubID = getAccountID(bidReq.App.Publisher) } else { // both bidReq.App == nil and bidReq.Site != nil are true labels.Source = pbsmetrics.DemandWeb if usersyncs.LiveSyncCount() == 0 { @@ -250,7 +250,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } else { labels.CookieFlag = pbsmetrics.CookieFlagYes } - labels.PubID = effectivePubID(bidReq.Site.Publisher) + labels.PubID = getAccountID(bidReq.Site.Publisher) } if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { diff --git a/exchange/utils.go b/exchange/utils.go index 2131aac5f41..2e9e4dc8f80 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -206,14 +206,15 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s } // set source - var source openrtb.Source + if req.Source == nil { + req.Source = &openrtb.Source{} + } schain := openrtb_ext.ExtRequestPrebidSChain{ SChain: *selectedSChain, } sourceExt, err := json.Marshal(schain) if err == nil { - source.Ext = sourceExt - req.Source = &source + req.Source.Ext = sourceExt } } From 0c96441954e9443d11a591daa62da86cba14cde1 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Thu, 3 Sep 2020 07:50:18 -0700 Subject: [PATCH 189/603] Add support for Account configuration (PBID-727, #1395) (#1426) --- analytics/core.go | 2 + config/accounts.go | 8 + config/config.go | 44 ++++- config/config_test.go | 17 ++ config/stored_requests.go | 36 +++- endpoints/openrtb2/amp_auction.go | 34 ++-- endpoints/openrtb2/amp_auction_test.go | 14 +- endpoints/openrtb2/auction.go | 74 ++++++-- endpoints/openrtb2/auction_benchmark_test.go | 1 + endpoints/openrtb2/auction_test.go | 172 +++++++++++++++--- .../account-required/valid-acct.json | 67 +++++++ endpoints/openrtb2/video_auction.go | 14 +- endpoints/openrtb2/video_auction_test.go | 7 +- exchange/exchange.go | 8 +- exchange/exchange_test.go | 8 +- exchange/targeting_test.go | 2 +- router/router.go | 8 +- .../backends/db_fetcher/fetcher.go | 4 + .../backends/empty_fetcher/fetcher.go | 5 + .../backends/file_fetcher/fetcher.go | 15 ++ .../backends/file_fetcher/fetcher_test.go | 14 ++ .../file_fetcher/test/accounts/valid.json | 4 + .../backends/http_fetcher/fetcher.go | 4 + stored_requests/config/config.go | 5 +- stored_requests/data/by_id/accounts/test.json | 14 ++ stored_requests/fetcher.go | 10 + stored_requests/fetcher_test.go | 5 + stored_requests/multifetcher.go | 15 ++ stored_requests/multifetcher_test.go | 51 ++++++ 29 files changed, 576 insertions(+), 86 deletions(-) create mode 100644 config/accounts.go create mode 100644 endpoints/openrtb2/sample-requests/account-required/valid-acct.json create mode 100644 stored_requests/backends/file_fetcher/test/accounts/valid.json create mode 100644 stored_requests/data/by_id/accounts/test.json diff --git a/analytics/core.go b/analytics/core.go index 6fd5139fd3d..737d133487e 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -2,6 +2,7 @@ package analytics import ( "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" ) @@ -28,6 +29,7 @@ type AuctionObject struct { Errors []error Request *openrtb.BidRequest Response *openrtb.BidResponse + Account *config.Account } //Loggable object of a transaction at /openrtb2/amp endpoint diff --git a/config/accounts.go b/config/accounts.go new file mode 100644 index 00000000000..11c2e10eb1b --- /dev/null +++ b/config/accounts.go @@ -0,0 +1,8 @@ +package config + +// Account represents a publisher account configuration +type Account struct { + ID string `mapstructure:"id" json:"id"` + Disabled bool `mapstructure:"disabled" json:"disabled"` + CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` +} diff --git a/config/config.go b/config/config.go index 23cc35719db..59ba55ebe26 100755 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "encoding/json" "errors" "fmt" "net/url" @@ -40,6 +41,7 @@ type Configuration struct { StoredRequests StoredRequests `mapstructure:"stored_requests"` StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` CategoryMapping StoredRequests `mapstructure:"category_mapping"` + Accounts StoredRequests `mapstructure:"accounts"` // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. StoredVideo StoredRequests `mapstructure:"stored_video_req"` @@ -65,6 +67,11 @@ type Configuration struct { BlacklistedAcctMap map[string]bool // Is publisher/account ID required to be submitted in the OpenRTB2 request AccountRequired bool `mapstructure:"account_required"` + // AccountDefaults defines default settings for valid accounts that are partially defined + // and provides a way to set global settings that can be overridden at account level. + AccountDefaults Account `mapstructure:"account_defaults"` + // accountDefaultsJSON is the internal serialized form of AccountDefaults used for json merge + accountDefaultsJSON json.RawMessage // Local private file containing SSL certificates PemCertsFile string `mapstructure:"certificates_file"` // Custom headers to handle request timeouts from queueing infrastructure @@ -106,10 +113,11 @@ func (c configErrors) Error() string { func (cfg *Configuration) validate() configErrors { var errs configErrors errs = cfg.AuctionTimeouts.validate(errs) - errs = cfg.StoredRequests.validate("stored_req", errs) - errs = cfg.StoredRequestsAMP.validate("stored_amp_req", errs) - errs = cfg.CategoryMapping.validate("categories", errs) - errs = cfg.StoredVideo.validate("stored_video_req", errs) + errs = cfg.StoredRequests.validate(errs) + errs = cfg.StoredRequestsAMP.validate(errs) + errs = cfg.Accounts.validate(errs) + errs = cfg.CategoryMapping.validate(errs) + errs = cfg.StoredVideo.validate(errs) errs = cfg.Metrics.validate(errs) if cfg.MaxRequestSize < 0 { errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize)) @@ -119,6 +127,9 @@ func (cfg *Configuration) validate() configErrors { errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) errs = cfg.ExtCacheURL.validate(errs) + if cfg.AccountDefaults.Disabled { + glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`) + } return errs } @@ -589,6 +600,12 @@ func New(v *viper.Viper) (*Configuration, error) { return nil, err } + // Update account defaults and generate base json for patch + c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config + if err := c.MarshalAccountDefaults(); err != nil { + return nil, err + } + // To look for a request's publisher_id in the NonStandardPublishers list in // O(1) time, we fill this hash table located in the NonStandardPublisherMap field of GDPR c.GDPR.NonStandardPublisherMap = make(map[string]int) @@ -622,6 +639,20 @@ func New(v *viper.Viper) (*Configuration, error) { return &c, nil } +// MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch +func (cfg *Configuration) MarshalAccountDefaults() error { + var err error + if cfg.accountDefaultsJSON, err = json.Marshal(cfg.AccountDefaults); err != nil { + glog.Warningf("converting %+v to json: %v", cfg.AccountDefaults, err) + } + return err +} + +// AccountDefaultsJSON returns the precompiled JSON form of account_defaults +func (cfg *Configuration) AccountDefaultsJSON() json.RawMessage { + return cfg.accountDefaultsJSON +} + //Allows for protocol relative URL if scheme is empty func (cfg *Cache) GetBaseURL() string { cfg.Scheme = strings.ToLower(cfg.Scheme) @@ -843,6 +874,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("stored_video_req.http_events.refresh_rate_seconds", 0) v.SetDefault("stored_video_req.http_events.timeout_ms", 0) + v.SetDefault("accounts.filesystem.enabled", false) + v.SetDefault("accounts.filesystem.directorypath", "./stored_requests/data/by_id") + v.SetDefault("accounts.in_memory_cache.type", "none") + for _, bidder := range openrtb_ext.BidderMap { setBidderDefaults(v, strings.ToLower(string(bidder))) } @@ -976,6 +1011,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("blacklisted_apps", []string{""}) v.SetDefault("blacklisted_accts", []string{""}) v.SetDefault("account_required", false) + v.SetDefault("account_defaults.disabled", false) v.SetDefault("certificates_file", "") v.SetDefault("auto_gen_source_tid", true) diff --git a/config/config_test.go b/config/config_test.go index 23764d216ef..d2e80c7e14c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "errors" "net" "os" "strings" @@ -466,6 +467,10 @@ func TestValidConfig(t *testing.T) { CategoryMapping: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, }, + Accounts: StoredRequests{ + Files: FileFetcherConfig{Enabled: true}, + InMemoryCache: InMemoryCache{Type: "none"}, + }, } resolvedStoredRequestsConfig(&cfg) @@ -640,6 +645,18 @@ func TestValidateDebug(t *testing.T) { assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed") } +func TestValidateAccountsConfigRestrictions(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.Accounts.Files.Enabled = true + cfg.Accounts.HTTP.Endpoint = "http://localhost" + cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts" + + errs := cfg.validate() + assert.Len(t, errs, 2) + assert.Contains(t, errs, errors.New("accounts.http: retrieving accounts via http not available, use accounts.files")) + assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files")) +} + func newDefaultConfig(t *testing.T) *Configuration { v := viper.New() SetupViper(v, "") diff --git a/config/stored_requests.go b/config/stored_requests.go index b63073fede7..61db7eb03d0 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -18,8 +18,20 @@ const ( CategoryDataType DataType = "Category" VideoDataType DataType = "Video" AMPRequestDataType DataType = "AMP Request" + AccountDataType DataType = "Account" ) +// Section returns the config section this type is defined in +func (sr *StoredRequests) Section() string { + return map[DataType]string{ + RequestDataType: "stored_requests", + CategoryDataType: "categories", + VideoDataType: "stored_video_req", + AMPRequestDataType: "stored_amp_req", + AccountDataType: "accounts", + }[sr.dataType] +} + func (sr *StoredRequests) DataType() DataType { return sr.dataType } @@ -109,34 +121,42 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredRequestsAMP.dataType = AMPRequestDataType cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType + cfg.Accounts.dataType = AccountDataType return } -func (cfg *StoredRequests) validate(section string, errs configErrors) configErrors { - errs = cfg.Postgres.validate(section, errs) +func (cfg *StoredRequests) validate(errs configErrors) configErrors { + if cfg.DataType() == AccountDataType && cfg.HTTP.Endpoint != "" { + errs = append(errs, fmt.Errorf("%s.http: retrieving accounts via http not available, use accounts.files", cfg.Section())) + } + if cfg.DataType() == AccountDataType && cfg.Postgres.ConnectionInfo.Database != "" { + errs = append(errs, fmt.Errorf("%s.postgres: retrieving accounts via postgres not available, use accounts.files", cfg.Section())) + } else { + errs = cfg.Postgres.validate(cfg.Section(), errs) + } // Categories do not use cache so none of the following checks apply - if cfg.dataType == CategoryDataType { + if cfg.DataType() == CategoryDataType { return errs } if cfg.InMemoryCache.Type == "none" { if cfg.CacheEvents.Enabled { - errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", section)) + errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", cfg.Section())) } if cfg.HTTPEvents.RefreshRate != 0 { - errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", section)) + errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", cfg.Section())) } if cfg.Postgres.PollUpdates.Query != "" { - errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", section)) + errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section())) } if cfg.Postgres.CacheInitialization.Query != "" { - errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", section)) + errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section())) } } - errs = cfg.InMemoryCache.validate(section, errs) + errs = cfg.InMemoryCache.validate(cfg.Section(), errs) return errs } diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index f5b72e029e0..54f4706902d 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -44,6 +44,7 @@ func NewAmpEndpoint( ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, + accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, @@ -53,7 +54,7 @@ func NewAmpEndpoint( bidderMap map[string]openrtb_ext.BidderName, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { + if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { return nil, errors.New("NewAmpEndpoint requires non-nil arguments.") } @@ -69,6 +70,7 @@ func NewAmpEndpoint( validator, requestsById, empty_fetcher.EmptyFetcher{}, + accounts, categories, cfg, met, @@ -155,26 +157,30 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h labels.CookieFlag = pbsmetrics.CookieFlagYes } labels.PubID = getAccountID(req.Site.Publisher) - - // Blacklist account now that we have resolved the value - if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { - errL = append(errL, acctIdErr) - errCode := errortypes.ReadCode(acctIdErr) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { - w.WriteHeader(http.StatusServiceUnavailable) - labels.RequestStatus = pbsmetrics.RequestStatusBlacklisted - } else { - w.WriteHeader(http.StatusBadRequest) - labels.RequestStatus = pbsmetrics.RequestStatusBadInput + // Look up account now that we have resolved the pubID value + account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + if len(acctIDErrs) > 0 { + errL = append(errL, acctIDErrs...) + httpStatus := http.StatusBadRequest + metricsStatus := pbsmetrics.RequestStatusBadInput + for _, er := range errL { + errCode := errortypes.ReadCode(er) + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { + httpStatus = http.StatusServiceUnavailable + metricsStatus = pbsmetrics.RequestStatusBlacklisted + break + } } + w.WriteHeader(httpStatus) + labels.RequestStatus = metricsStatus for _, err := range errortypes.FatalOnly(errL) { w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error()))) } - ao.Errors = append(ao.Errors, acctIdErr) + ao.Errors = append(ao.Errors, acctIDErrs...) return } - response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil) + response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil) ao.AuctionResponse = response if err != nil { diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index be6735c4c39..57e0a7b447d 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -47,6 +47,7 @@ func TestGoodAmpRequests(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{goodRequests}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -100,6 +101,7 @@ func TestAMPPageInfo(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -205,6 +207,7 @@ func TestGDPRConsent(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -358,6 +361,7 @@ func TestCCPAConsent(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -417,6 +421,7 @@ func TestNoConsent(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -463,6 +468,7 @@ func TestInvalidConsent(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -547,6 +553,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -599,6 +606,7 @@ func TestAMPSiteExt(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -639,6 +647,7 @@ func TestAmpBadRequests(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{badRequests}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -670,6 +679,7 @@ func TestAmpDebug(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -743,6 +753,7 @@ func TestQueryParamOverrides(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -895,6 +906,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -952,7 +964,7 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi }, } -func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest response := &openrtb.BidResponse{ diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bad2f8aae5b..bc0cd90073f 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -46,9 +46,9 @@ var ( dntEnabled int8 = 1 ) -func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) { +func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { + if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { return nil, errors.New("NewEndpoint requires non-nil arguments.") } @@ -64,6 +64,7 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato validator, requestsById, empty_fetcher.EmptyFetcher{}, + accounts, categories, cfg, met, @@ -82,6 +83,7 @@ type endpointDeps struct { paramsValidator openrtb_ext.BidderParamValidator storedReqFetcher stored_requests.Fetcher videoFetcher stored_requests.Fetcher + accounts stored_requests.AccountFetcher categories stored_requests.CategoryFetcher cfg *config.Configuration metricsEngine pbsmetrics.MetricsEngine @@ -153,15 +155,18 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http labels.PubID = getAccountID(req.Site.Publisher) } - if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { - errL = append(errL, acctIdErr) + // Look up account now that we have resolved the pubID value + account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + if len(acctIDErrs) > 0 { + errL = append(errL, acctIDErrs...) writeError(errL, w, &labels) return } - response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil) + response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil) ao.Request = req ao.Response = response + ao.Account = account if err != nil { labels.RequestStatus = pbsmetrics.RequestStatusErr w.WriteHeader(http.StatusInternalServerError) @@ -1305,14 +1310,55 @@ func getAccountID(pub *openrtb.Publisher) string { return pbsmetrics.PublisherUnknown } -func validateAccount(cfg *config.Configuration, pubID string) error { - var err error = nil - if cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown { - // If specified in the configuration, discard requests that don't come with an account ID. - err = error(&errortypes.AcctRequired{Message: fmt.Sprintf("Prebid-server has been configured to discard requests that don't come with an Account ID. Please reach out to the prebid server host.")}) - } else if _, found := cfg.BlacklistedAcctMap[pubID]; found { - // Blacklist account now that we have resolved the value - err = error(&errortypes.BlacklistedAcct{Message: fmt.Sprintf("Prebid-server has blacklisted Account ID: %s, please reach out to the prebid server host.", pubID)}) +func (deps *endpointDeps) getAccount(ctx context.Context, pubID string) (account *config.Account, errs []error) { + // Check BlacklistedAcctMap until we have deprecated it + if _, found := deps.cfg.BlacklistedAcctMap[pubID]; found { + return nil, []error{&errortypes.BlacklistedAcct{ + Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID), + }} + } + if deps.cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown { + return nil, []error{&errortypes.AcctRequired{ + Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), + }} + } + if accountJSON, accErrs := deps.accounts.FetchAccount(ctx, pubID); len(accErrs) > 0 || accountJSON == nil { + // pubID does not reference a valid account + if len(accErrs) > 0 { + errs = append(errs, errs...) + } + if deps.cfg.AccountRequired && deps.cfg.AccountDefaults.Disabled { + errs = append(errs, &errortypes.AcctRequired{ + Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), + }) + return nil, errs + } + // Make a copy of AccountDefaults instead of taking a reference, + // to preserve original pubID in case is needed to check NonStandardPublisherMap + pubAccount := deps.cfg.AccountDefaults + pubAccount.ID = pubID + account = &pubAccount + } else { + // pubID resolved to a valid account, merge with AccountDefaults for a complete config + account = &config.Account{} + completeJSON, err := jsonpatch.MergePatch(deps.cfg.AccountDefaultsJSON(), accountJSON) + if err == nil { + err = json.Unmarshal(completeJSON, account) + } + if err != nil { + errs = append(errs, err) + return nil, errs + } + // Fill in ID if needed, so it can be left out of account definition + if len(account.ID) == 0 { + account.ID = pubID + } } - return err + if account.Disabled { + errs = append(errs, &errortypes.BlacklistedAcct{ + Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID), + }) + return nil, errs + } + return } diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index fba0daecea8..09af23af103 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -83,6 +83,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { paramValidator, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 6baab8d500f..72da2a36953 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -38,16 +38,17 @@ const maxSize = 1024 * 256 // Struct of data for the general purpose auction tester type getResponseFromDirectory struct { - dir string - file string - payloadGetter func(*testing.T, []byte) []byte - messageGetter func(*testing.T, []byte) []byte - expectedCode int - aliased bool - disabledBidders []string - adaptersConfig map[string]config.Adapter - accountReq bool - description string + dir string + file string + payloadGetter func(*testing.T, []byte) []byte + messageGetter func(*testing.T, []byte) []byte + expectedCode int + aliased bool + disabledBidders []string + adaptersConfig map[string]config.Adapter + accountReq bool + accountDefaultDisabled bool + description string } // TestExplicitUserId makes sure that the cookie's ID doesn't override an explicit value sent in the request. @@ -103,7 +104,7 @@ func TestExplicitUserId(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) endpoint(httptest.NewRecorder(), request, nil) @@ -255,7 +256,7 @@ func TestRejectAccountRequired(t *testing.T) { accountReq: true, }, { - // Account is required, was provided and is not in the blacklisted accounts map + // Account is required, was provided, not blacklisted, is not defined by host dir: "sample-requests/account-required", file: "with-acct.json", payloadGetter: getRequestPayload, @@ -264,6 +265,28 @@ func TestRejectAccountRequired(t *testing.T) { aliased: true, accountReq: true, }, + { + // Account is required, was provided, not blacklisted, is not defined by host + // but strict validation is in force because default account settings are disabled. + dir: "sample-requests/account-required", + file: "with-acct.json", + payloadGetter: getRequestPayload, + messageGetter: nilReturner, + expectedCode: http.StatusBadRequest, + aliased: true, + accountReq: true, + accountDefaultDisabled: true, + }, + { + // Account is required, was provided, not blacklisted and is a valid account + dir: "sample-requests/account-required", + file: "valid-acct.json", + payloadGetter: getRequestPayload, + messageGetter: nilReturner, + expectedCode: http.StatusOK, + aliased: true, + accountReq: true, + }, { // Account is required, was provided in request and is found in the blacklisted accounts map dir: "sample-requests/blacklisted", @@ -346,12 +369,23 @@ func (gr *getResponseFromDirectory) doRequest(t *testing.T, requestData []byte) // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + cfg := config.Configuration{ + MaxRequestSize: maxSize, + BlacklistedApps: []string{"spam_app"}, + BlacklistedAppMap: map[string]bool{"spam_app": true}, + BlacklistedAccts: []string{"bad_acct"}, + BlacklistedAcctMap: map[string]bool{"bad_acct": true}, + AccountRequired: gr.accountReq, + AccountDefaults: config.Account{Disabled: gr.accountDefaultDisabled}, + } + assert.NoError(t, cfg.MarshalAccountDefaults()) endpoint, _ := NewEndpoint( &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, + &mockAccountFetcher{}, empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize, BlacklistedApps: []string{"spam_app"}, BlacklistedAppMap: map[string]bool{"spam_app": true}, BlacklistedAccts: []string{"bad_acct"}, BlacklistedAcctMap: map[string]bool{"bad_acct": true}, AccountRequired: gr.accountReq}, + &cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, @@ -390,7 +424,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap) + endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap) request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(requestData)) recorder := httptest.NewRecorder() @@ -454,7 +488,7 @@ func TestNilExchange(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - _, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + _, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil Exchange.") } @@ -465,7 +499,7 @@ func TestNilValidator(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - _, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + _, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.") } @@ -476,7 +510,7 @@ func TestExchangeError(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() endpoint(recorder, request, nil) @@ -588,7 +622,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { IPv6PrivateNetworksParsed: test.privateNetworksIPv6, }, } - endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader) @@ -773,7 +807,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) for _, test := range testCases { exchange := &nobidExchange{} - endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) + endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("DNT", test.dntHeader) @@ -846,6 +880,7 @@ func TestStoredRequests(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -885,6 +920,7 @@ func TestOversizedRequest(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -920,6 +956,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -953,6 +990,7 @@ func TestNoEncoding(t *testing.T) { newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1028,6 +1066,7 @@ func TestContentType(t *testing.T) { newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1058,6 +1097,7 @@ func TestDisabledBidder(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{ MaxRequestSize: int64(len(reqBody)), }, @@ -1096,6 +1136,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(8096)}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1127,6 +1168,7 @@ func TestCurrencyTrunc(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1171,6 +1213,7 @@ func TestCCPAInvalid(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1223,6 +1266,7 @@ func TestValidateSourceTID(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1267,6 +1311,7 @@ func TestSChainInvalid(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{}, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1487,7 +1532,7 @@ type nobidExchange struct { gotRequest *openrtb.BidRequest } -func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { e.gotRequest = bidRequest return &openrtb.BidResponse{ ID: bidRequest.ID, @@ -1498,7 +1543,7 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.Bid type brokenExchange struct{} -func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { return nil, errors.New("Critical, unrecoverable error.") } @@ -1854,11 +1899,27 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s return testStoredRequestData, testStoredImpData, nil } +var mockAccountData = map[string]json.RawMessage{ + "valid_acct": json.RawMessage(`{"disabled":false}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), +} + +type mockAccountFetcher struct { +} + +func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + if account, ok := mockAccountData[accountID]; ok { + return account, nil + } else { + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + } +} + type mockExchange struct { lastRequest *openrtb.BidRequest } -func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest return &openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{{ @@ -1913,3 +1974,70 @@ type hardcodedResponseIPValidator struct { func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { return v.response } + +func TestGetAccount(t *testing.T) { + unknown := pbsmetrics.PublisherUnknown + testCases := []struct { + accountID string + // account_required + required bool + // account_defaults.disabled + disabled bool + // expected error, or nil if account should be found + err error + }{ + // Blacklisted account is always rejected even in permissive setup + {accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, + + // empty pubID + {accountID: unknown, required: false, disabled: false, err: nil}, + {accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}}, + {accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, + + // pubID given but is not a valid host account (does not exist) + {accountID: "not_bad_acct", required: false, disabled: false, err: nil}, + {accountID: "not_bad_acct", required: true, disabled: false, err: nil}, + {accountID: "not_bad_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "not_bad_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, + + // pubID given and matches a valid host account with Disabled: false + {accountID: "valid_acct", required: false, disabled: false, err: nil}, + {accountID: "valid_acct", required: true, disabled: false, err: nil}, + {accountID: "valid_acct", required: false, disabled: true, err: nil}, + {accountID: "valid_acct", required: true, disabled: true, err: nil}, + + // pubID given and matches a host account explicitly disabled (Disabled: true on account json) + {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, + } + + for _, test := range testCases { + description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled) + t.Run(description, func(t *testing.T) { + deps := &endpointDeps{ + cfg: &config.Configuration{ + BlacklistedAcctMap: map[string]bool{"bad_acct": true}, + AccountRequired: test.required, + AccountDefaults: config.Account{Disabled: test.disabled}, + }, + accounts: &mockAccountFetcher{}, + } + assert.NoError(t, deps.cfg.MarshalAccountDefaults()) + + account, errors := deps.getAccount(context.Background(), test.accountID) + + if test.err == nil { + assert.Empty(t, errors) + assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID") + assert.Equal(t, false, account.Disabled, "returned account must not be disabled") + } else { + assert.NotEmpty(t, errors, "expected errors but got success") + assert.Nil(t, account, "return account must be nil on error") + assert.IsType(t, test.err, errors[0], "error is of unexpected type") + } + }) + } +} diff --git a/endpoints/openrtb2/sample-requests/account-required/valid-acct.json b/endpoints/openrtb2/sample-requests/account-required/valid-acct.json new file mode 100644 index 00000000000..87d9d1eac37 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/account-required/valid-acct.json @@ -0,0 +1,67 @@ +{ + "description": "This request comes with a valid account id", + "message": "", + + "requestPayload": { + "id": "some-request-id", + "site": { + "publisher": { "id": "valid_acct"}, + "page": "test.somepage.com" + }, + "user": { }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus" + }, + "bidadjustmentfactors": { + "appnexus": 1.01, + "districtm": 0.98, + "rubicon": 0.99 + }, + "cache": { + "bids": {} + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + } + } + diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index a8e4c28b167..f5494751cc2 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -35,9 +35,9 @@ import ( var defaultRequestTimeout int64 = 5000 -func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) { +func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil { + if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { return nil, errors.New("NewVideoEndpoint requires non-nil arguments.") } @@ -55,6 +55,7 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal validator, requestsById, videoFetcher, + accounts, categories, cfg, met, @@ -253,13 +254,14 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re labels.PubID = getAccountID(bidReq.Site.Publisher) } - if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil { - errL := []error{err} - handleError(&labels, w, errL, &vo, &debugLog) + // Look up account now that we have resolved the pubID value + account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + if len(acctIDErrs) > 0 { + handleError(&labels, w, acctIDErrs, &vo, &debugLog) return } //execute auction logic - response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories, &debugLog) + response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, account, &deps.categories, &debugLog) vo.Request = bidReq vo.Response = response if err != nil { diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 78715f5c87d..a4e9bfbb61e 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1168,6 +1168,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, mockModule, @@ -1210,6 +1211,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1233,6 +1235,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), @@ -1275,7 +1278,7 @@ type mockExchangeVideo struct { cache *mockCacheClient } -func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest if debugLog != nil && debugLog.Enabled { m.cache.called = true @@ -1311,7 +1314,7 @@ type mockExchangeVideoNoBids struct { cache *mockCacheClient } -func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { m.lastRequest = bidRequest return &openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{{}}, diff --git a/exchange/exchange.go b/exchange/exchange.go index 59e876697cf..9d1cd20affa 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -37,7 +37,7 @@ const DebugContextKey = ContextKey("debugInfo") // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines. type Exchange interface { // HoldAuction executes an OpenRTB v2.5 Auction. - HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) + HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) } // IdFetcher can find the user's ID for a specific Bidder. @@ -54,7 +54,6 @@ type exchange struct { gDPR gdpr.Permissions currencyConverter *currencies.RateConverter UsersyncIfAmbiguous bool - defaultTTLs config.DefaultTTLs privacyConfig config.Privacy eeaCountries map[string]struct{} } @@ -89,7 +88,6 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con e.gDPR = gDPR e.currencyConverter = currencyConverter e.UsersyncIfAmbiguous = cfg.GDPR.UsersyncIfAmbiguous - e.defaultTTLs = cfg.CacheURL.DefaultTTLs e.privacyConfig = config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -98,7 +96,7 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con return e } -func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) { +func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) { requestExt, err := extractBidRequestExt(bidRequest) if err != nil { @@ -203,7 +201,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque } } - cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog) + cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &account.CacheTTL, bidCategory, debugLog) if len(cacheErrs) > 0 { errs = append(errs, cacheErrs...) } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index efabb845211..21003b669ed 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -248,7 +248,7 @@ func TestDebugBehaviour(t *testing.T) { } // Run test - outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) + outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil) // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) @@ -752,7 +752,7 @@ func TestRaceIntegration(t *testing.T) { theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)) ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter) - _, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) + _, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -939,7 +939,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - _, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) + _, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -1055,7 +1055,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { *debugLog = *spec.DebugLog debugLog.Regexp = regexp.MustCompile(`[<>]`) } - bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher, debugLog) + bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, debugLog) responseTimes := extractResponseTimes(t, filename, bid) for _, bidderName := range biddersInAuction { if _, ok := responseTimes[bidderName]; !ok { diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index e596e5aa215..aaa75411ee4 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -108,7 +108,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher, nil) + bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil) if err != nil { t.Fatalf("Unexpected errors running auction: %v", err) diff --git a/router/router.go b/router/router.go index 30936705a22..4826f0e5feb 100644 --- a/router/router.go +++ b/router/router.go @@ -211,7 +211,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r // Metrics engine r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList) - db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) + db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown @@ -243,19 +243,19 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor) - openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) + openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) if err != nil { glog.Fatalf("Failed to create the openrtb endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) + ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient) + videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index 223067c917e..d8cf132d25b 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -93,6 +93,10 @@ func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string return storedRequestData, storedImpData, errs } +func (fetcher *dbFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} +} + func (fetcher *dbFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go index 25e8ead434b..ee6b98b3b2e 100644 --- a/stored_requests/backends/empty_fetcher/fetcher.go +++ b/stored_requests/backends/empty_fetcher/fetcher.go @@ -3,6 +3,7 @@ package empty_fetcher import ( "context" "encoding/json" + "github.com/prebid/prebid-server/stored_requests" ) @@ -27,6 +28,10 @@ func (fetcher EmptyFetcher) FetchRequests(ctx context.Context, requestIDs []stri return } +func (fetcher EmptyFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} +} + func (fetcher EmptyFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index 60853f65da7..2d3b00657b9 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -33,6 +33,21 @@ func (fetcher *eagerFetcher) FetchRequests(ctx context.Context, requestIDs []str return storedRequests, storedImpressions, errs } +// FetchAccount fetches the host account configuration for a publisher +func (fetcher *eagerFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + if len(accountID) == 0 { + return nil, []error{fmt.Errorf("Cannot look up an empty accountID")} + } + accountJSON, ok := fetcher.FileSystem.Directories["accounts"].Files[accountID] + if !ok { + return nil, []error{stored_requests.NotFoundError{ + ID: accountID, + DataType: "Account", + }} + } + return accountJSON, nil +} + func (fetcher *eagerFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { fileName := primaryAdServer diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index 2429a77cd25..a145a3b43a2 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -24,6 +24,20 @@ func TestFileFetcher(t *testing.T) { validateImp(t, storedImps) } +func TestAccountFetcher(t *testing.T) { + fetcher, err := NewFileFetcher("./test") + assert.NoError(t, err, "Failed to create test fetcher") + + account, errs := fetcher.FetchAccount(context.Background(), "valid") + assertErrorCount(t, 0, errs) + assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account)) + + account, errs = fetcher.FetchAccount(context.Background(), "nonexistent") + assertErrorCount(t, 1, errs) + assert.Error(t, errs[0]) + assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) +} + func TestInvalidDirectory(t *testing.T) { _, err := NewFileFetcher("./nonexistant-directory") if err == nil { diff --git a/stored_requests/backends/file_fetcher/test/accounts/valid.json b/stored_requests/backends/file_fetcher/test/accounts/valid.json new file mode 100644 index 00000000000..2c8bd12af3c --- /dev/null +++ b/stored_requests/backends/file_fetcher/test/accounts/valid.json @@ -0,0 +1,4 @@ +{ + "id": "valid", + "disabled": false +} diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index b7e42c9e6cf..d533d5315ab 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -81,6 +81,10 @@ func (fetcher *HttpFetcher) FetchRequests(ctx context.Context, requestIDs []stri return } +func (fetcher *HttpFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} +} + func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { if fetcher.Categories == nil { fetcher.Categories = make(map[string]map[string]stored_requests.Category) diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 8f06efcb32b..e81d9667a73 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -106,7 +106,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine pbsmetrics.M // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { +func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -118,6 +118,7 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, &dbc) fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, &dbc) fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) + fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) db = dbc.db @@ -125,12 +126,14 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri ampFetcher = fetcher2.(stored_requests.Fetcher) categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) videoFetcher = fetcher4.(stored_requests.Fetcher) + accountsFetcher = fetcher5.(stored_requests.AccountFetcher) shutdown = func() { shutdown1() shutdown2() shutdown3() shutdown4() + shutdown5() } return diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json new file mode 100644 index 00000000000..76bafff7f1c --- /dev/null +++ b/stored_requests/data/by_id/accounts/test.json @@ -0,0 +1,14 @@ +{ + "id": "test", + "name": "test account", + "disabled": true, + "cache_ttl": { + "banner": 600, + "video": 3600, + "native": 3600, + "audio": 3600 + }, + "events": { + "enabled": true + } +} diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 23fdb6b4925..a31b9989bd0 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -25,6 +25,11 @@ type Fetcher interface { FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) } +type AccountFetcher interface { + // FetchAccount fetches the host account configuration for a publisher + FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) +} + type CategoryFetcher interface { // FetchCategories fetches the ad-server/publisher specific category for the given IAB category FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) @@ -33,6 +38,7 @@ type CategoryFetcher interface { // AllFetcher is an interface that encapsulates both the original Fetcher and the CategoryFetcher type AllFetcher interface { FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) + FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) } @@ -181,6 +187,10 @@ func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []strin return } +func (f *fetcherWithCache) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + return f.fetcher.FetchAccount(ctx, accountID) +} + func (f *fetcherWithCache) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go index c1040acdb90..1928d1165db 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -215,6 +215,11 @@ func (f *mockFetcher) FetchRequests(ctx context.Context, requestIDs []string, im return args.Get(0).(map[string]json.RawMessage), args.Get(1).(map[string]json.RawMessage), args.Get(2).([]error) } +func (a *mockFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + args := a.Called(ctx, accountID) + return args.Get(0).(json.RawMessage), args.Get(1).([]error) +} + func (f *mockFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } diff --git a/stored_requests/multifetcher.go b/stored_requests/multifetcher.go index 24cf848448c..2d08fd45337 100644 --- a/stored_requests/multifetcher.go +++ b/stored_requests/multifetcher.go @@ -36,6 +36,21 @@ func (mf MultiFetcher) FetchRequests(ctx context.Context, requestIDs []string, i return } +func (mf MultiFetcher) FetchAccount(ctx context.Context, accountID string) (account json.RawMessage, errs []error) { + for _, f := range mf { + if af, ok := f.(AccountFetcher); ok { + if account, accErrs := af.FetchAccount(ctx, accountID); len(accErrs) == 0 { + return account, nil + } else { + accErrs = dropMissingIDs(accErrs) + errs = append(errs, accErrs...) + } + } + } + errs = append(errs, NotFoundError{accountID, "Account"}) + return nil, errs +} + func (mf MultiFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { for _, f := range mf { if cf, ok := f.(CategoryFetcher); ok { diff --git a/stored_requests/multifetcher_test.go b/stored_requests/multifetcher_test.go index e703c2c9dcc..5035cfba82e 100644 --- a/stored_requests/multifetcher_test.go +++ b/stored_requests/multifetcher_test.go @@ -125,3 +125,54 @@ func TestOtherError(t *testing.T) { assert.JSONEq(t, `{"req_id": "def"}`, string(reqData["def"]), "MultiFetcher should return the right request data") assert.JSONEq(t, `{"imp_id": "imp-1"}`, string(impData["imp-1"]), "MultiFetcher should return the right imp data") } + +func TestMultiFetcherAccountFoundInFirstFetcher(t *testing.T) { + f1 := &mockFetcher{} + f2 := &mockFetcher{} + fetcher := &MultiFetcher{f1, f2} + ctx := context.Background() + + f1.On("FetchAccount", ctx, "ONE").Once().Return(json.RawMessage(`{"id": "ONE"}`), []error{}) + + account, errs := fetcher.FetchAccount(ctx, "ONE") + + f1.AssertExpectations(t) + f2.AssertNotCalled(t, "FetchAccount") + assert.Empty(t, errs) + assert.JSONEq(t, `{"id": "ONE"}`, string(account)) +} + +func TestMultiFetcherAccountFoundInSecondFetcher(t *testing.T) { + f1 := &mockFetcher{} + f2 := &mockFetcher{} + fetcher := &MultiFetcher{f1, f2} + ctx := context.Background() + + f1.On("FetchAccount", ctx, "TWO").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}}) + f2.On("FetchAccount", ctx, "TWO").Once().Return(json.RawMessage(`{"id": "TWO"}`), []error{}) + + account, errs := fetcher.FetchAccount(ctx, "TWO") + + f1.AssertExpectations(t) + f2.AssertExpectations(t) + assert.Empty(t, errs) + assert.JSONEq(t, `{"id": "TWO"}`, string(account)) +} + +func TestMultiFetcherAccountNotFound(t *testing.T) { + f1 := &mockFetcher{} + f2 := &mockFetcher{} + fetcher := &MultiFetcher{f1, f2} + ctx := context.Background() + + f1.On("FetchAccount", ctx, "MISSING").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}}) + f2.On("FetchAccount", ctx, "MISSING").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}}) + + account, errs := fetcher.FetchAccount(ctx, "MISSING") + + f1.AssertExpectations(t) + f2.AssertExpectations(t) + assert.Len(t, errs, 1) + assert.Nil(t, account) + assert.EqualError(t, errs[0], NotFoundError{"MISSING", "Account"}.Error()) +} From 44310b6f329813cc0f01747ea2e5c7db4f3975f9 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 8 Sep 2020 10:19:44 -0400 Subject: [PATCH 190/603] Minor changes to accounts test coverage (#1475) --- endpoints/openrtb2/auction_test.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 72da2a36953..53fea2e0500 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -279,13 +279,14 @@ func TestRejectAccountRequired(t *testing.T) { }, { // Account is required, was provided, not blacklisted and is a valid account - dir: "sample-requests/account-required", - file: "valid-acct.json", - payloadGetter: getRequestPayload, - messageGetter: nilReturner, - expectedCode: http.StatusOK, - aliased: true, - accountReq: true, + dir: "sample-requests/account-required", + file: "valid-acct.json", + payloadGetter: getRequestPayload, + messageGetter: nilReturner, + expectedCode: http.StatusOK, + aliased: true, + accountReq: true, + accountDefaultDisabled: true, }, { // Account is required, was provided in request and is found in the blacklisted accounts map @@ -1996,10 +1997,10 @@ func TestGetAccount(t *testing.T) { {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given but is not a valid host account (does not exist) - {accountID: "not_bad_acct", required: false, disabled: false, err: nil}, - {accountID: "not_bad_acct", required: true, disabled: false, err: nil}, - {accountID: "not_bad_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: "not_bad_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, + {accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil}, + {accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil}, + {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given and matches a valid host account with Disabled: false {accountID: "valid_acct", required: false, disabled: false, err: nil}, From d75df46922283b7cbeef8c14774f5bc8163a4c63 Mon Sep 17 00:00:00 2001 From: smithaammassamveettil <39389834+smithaammassamveettil@users.noreply.github.com> Date: Tue, 8 Sep 2020 10:53:49 -0700 Subject: [PATCH 191/603] Brightroll adapter - adding config support (#1461) --- adapters/brightroll/brightroll.go | 81 +++++++++++++++---- adapters/brightroll/brightroll_test.go | 28 ++++++- .../exemplary/banner-native-audio.json | 23 ++++-- .../exemplary/banner-video-native.json | 28 +++++-- .../exemplary/banner-video.json | 24 ++++-- .../exemplary/simple-banner.json | 15 +++- .../exemplary/simple-video.json | 15 +++- .../exemplary/valid-extension.json | 15 +++- .../exemplary/video-and-audio.json | 19 +++-- .../brightrolltest/params/race/banner.json | 2 +- .../brightrolltest/params/race/video.json | 2 +- .../supplemental/invalid-imp.json | 2 +- .../supplemental/invalid-publisher.json | 34 ++++++++ exchange/adapter_map.go | 2 +- 14 files changed, 236 insertions(+), 54 deletions(-) create mode 100644 adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json diff --git a/adapters/brightroll/brightroll.go b/adapters/brightroll/brightroll.go index 0ae95dd303a..83b253a0996 100644 --- a/adapters/brightroll/brightroll.go +++ b/adapters/brightroll/brightroll.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + "github.com/golang/glog" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" @@ -13,7 +14,20 @@ import ( ) type BrightrollAdapter struct { - URI string + URI string + extraInfo ExtraInfo +} + +type ExtraInfo struct { + Accounts []Account `json:"accounts"` +} + +type Account struct { + ID string `json:"id"` + Badv []string `json:"badv"` + Bcat []string `json:"bcat"` + Battr []int8 `json:"battr"` + BidFloor float64 `json:"bidfloor"` } func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -55,6 +69,23 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo errors = append(errors, err) return nil, errors } + + var account *Account + for _, a := range a.extraInfo.Accounts { + if a.ID == brightrollExt.Publisher { + account = &a + break + } + } + + if account == nil { + err = &errortypes.BadInput{ + Message: "Invalid publisher", + } + errors = append(errors, err) + return nil, errors + } + validImpExists := false for i := 0; i < len(request.Imp); i++ { //Brightroll supports only banner and video impressions as of now @@ -65,9 +96,9 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo bannerCopy.W = &(firstFormat.W) bannerCopy.H = &(firstFormat.H) } - if brightrollExt.Publisher == "adthrive" { - bannerCopy.BAttr = getBlockedCreativetypesForAdThrive() + if len(account.Battr) > 0 { + bannerCopy.BAttr = getBlockedCreativetypes(account.Battr) } request.Imp[i].Banner = &bannerCopy validImpExists = true @@ -75,10 +106,15 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo validImpExists = true if brightrollExt.Publisher == "adthrive" { videoCopy := *request.Imp[i].Video - videoCopy.BAttr = getBlockedCreativetypesForAdThrive() + if len(account.Battr) > 0 { + videoCopy.BAttr = getBlockedCreativetypes(account.Battr) + } request.Imp[i].Video = &videoCopy } } + if validImpExists && request.Imp[i].BidFloor == 0 && account.BidFloor > 0 { + request.Imp[i].BidFloor = account.BidFloor + } } if !validImpExists { err := &errortypes.BadInput{ @@ -90,8 +126,12 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo request.AT = 1 //Defaulting to first price auction for all prebid requests - if brightrollExt.Publisher == "adthrive" { - request.BCat = getBlockedCategoriesForAdthrive() + if len(account.Bcat) > 0 { + request.BCat = account.Bcat + } + + if len(account.Badv) > 0 { + request.BAdv = account.Badv } reqJSON, err := json.Marshal(request) if err != nil { @@ -159,13 +199,12 @@ func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, nil } -//customized request, need following blocked categories -func getBlockedCategoriesForAdthrive() []string { - return []string{"IAB8-5", "IAB8-18", "IAB15-1", "IAB7-30", "IAB14-1", "IAB22-1", "IAB3-7", "IAB7-3", "IAB14-3", "IAB11", "IAB11-1", "IAB11-2", "IAB11-3", "IAB11-4", "IAB11-5", "IAB23", "IAB23-1", "IAB23-2", "IAB23-3", "IAB23-4", "IAB23-5", "IAB23-6", "IAB23-7", "IAB23-8", "IAB23-9", "IAB23-10", "IAB7-39", "IAB9-30", "IAB7-44", "IAB25", "IAB25-1", "IAB25-2", "IAB25-3", "IAB25-4", "IAB25-5", "IAB25-6", "IAB25-7", "IAB26", "IAB26-1", "IAB26-2", "IAB26-3", "IAB26-4"} -} - -func getBlockedCreativetypesForAdThrive() []openrtb.CreativeAttribute { - return []openrtb.CreativeAttribute{openrtb.CreativeAttribute(1), openrtb.CreativeAttribute(2), openrtb.CreativeAttribute(3), openrtb.CreativeAttribute(6), openrtb.CreativeAttribute(9), openrtb.CreativeAttribute(10)} +func getBlockedCreativetypes(attr []int8) []openrtb.CreativeAttribute { + var creativeAttr []openrtb.CreativeAttribute + for i := 0; i < len(attr); i++ { + creativeAttr = append(creativeAttr, openrtb.CreativeAttribute(attr[i])) + } + return creativeAttr } //Adding header fields to request header @@ -189,8 +228,18 @@ func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { return mediaType } -func NewBrightrollBidder(endpoint string) *BrightrollAdapter { - return &BrightrollAdapter{ - URI: endpoint, +func NewBrightrollBidder(endpoint string, extraAdapterInfo string) *BrightrollAdapter { + + var extraInfo ExtraInfo + + if len(extraAdapterInfo) == 0 { + extraAdapterInfo = "{\"accounts\":[]}" + } + err := json.Unmarshal([]byte(extraAdapterInfo), &extraInfo) + + if err != nil { + glog.Fatalf("Invalid Brightroll extra adapter info: " + err.Error()) + return nil } + return &BrightrollAdapter{URI: endpoint, extraInfo: extraInfo} } diff --git a/adapters/brightroll/brightroll_test.go b/adapters/brightroll/brightroll_test.go index 0a6c2c44567..5f1c64fd16f 100644 --- a/adapters/brightroll/brightroll_test.go +++ b/adapters/brightroll/brightroll_test.go @@ -1,11 +1,37 @@ package brightroll import ( + "github.com/stretchr/testify/assert" "testing" "github.com/prebid/prebid-server/adapters/adapterstest" ) +func TestEmptyConfig(t *testing.T) { + output := NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "") + ex := ExtraInfo{ + Accounts: []Account{}, + } + expected := &BrightrollAdapter{ + URI: "http://test-bid.ybp.yahoo.com/bid/appnexuspbs", + extraInfo: ex, + } + assert.Equal(t, expected, output, "") +} + +func TestNonEmptyConfig(t *testing.T) { + output := NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "{\"accounts\": [{\"id\": \"test\",\"bidfloor\":0.1}]}") + ex := ExtraInfo{ + Accounts: []Account{{ID: "test", BidFloor: 0.1}}, + } + + expected := &BrightrollAdapter{ + URI: "http://test-bid.ybp.yahoo.com/bid/appnexuspbs", + extraInfo: ex, + } + assert.Equal(t, expected, output, "") +} + func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "brightrolltest", NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs")) + adapterstest.RunJSONBidderTest(t, "brightrolltest", NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "{\"accounts\": [{\"id\": \"adthrive\",\"badv\": [], \"bcat\": [\"IAB8-5\",\"IAB8-18\"],\"battr\": [1,2,3], \"bidfloor\":0.0}]}")) } diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json b/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json index e8ef5b688f6..e67a1485e54 100644 --- a/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json +++ b/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -30,7 +30,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -43,7 +43,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -52,14 +52,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "banner": { + "battr": [ + 1, + 2, + 3 + ], "format": [ { "w": 300, @@ -75,7 +84,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -87,7 +96,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -100,7 +109,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json b/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json index 4df7ab5df10..0fffbd2fac5 100644 --- a/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json +++ b/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -30,7 +30,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -44,7 +44,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -53,14 +53,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "banner": { + "battr": [ + 1, + 2, + 3 + ], "format": [ { "w": 300, @@ -76,7 +85,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -88,13 +97,18 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, { "id": "test-imp-video-id", "video": { + "battr": [ + 1, + 2, + 3 + ], "mimes": ["video/mp4"], "protocols": [2, 5], "w": 1024, @@ -102,7 +116,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-video.json b/adapters/brightroll/brightrolltest/exemplary/banner-video.json index 50053102d08..f58dd9b1395 100644 --- a/adapters/brightroll/brightrolltest/exemplary/banner-video.json +++ b/adapters/brightroll/brightrolltest/exemplary/banner-video.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -32,7 +32,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -41,14 +41,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "banner": { + "battr": [ + 1, + 2, + 3 + ], "format": [ { "w": 300, @@ -64,13 +73,18 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, { "id": "test-imp-video-id", "video": { + "battr": [ + 1, + 2, + 3 + ], "mimes": ["video/mp4"], "protocols": [2, 5], "w": 1024, @@ -78,7 +92,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json b/adapters/brightroll/brightrolltest/exemplary/simple-banner.json index 96fa0cbc9f3..66bc06cf696 100644 --- a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json +++ b/adapters/brightroll/brightrolltest/exemplary/simple-banner.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -28,14 +28,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "banner": { + "battr": [ + 1, + 2, + 3 + ], "format": [ { "w": 300, @@ -51,7 +60,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/simple-video.json b/adapters/brightroll/brightrolltest/exemplary/simple-video.json index f2466b2fd95..3f9b809182d 100644 --- a/adapters/brightroll/brightrolltest/exemplary/simple-video.json +++ b/adapters/brightroll/brightrolltest/exemplary/simple-video.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -22,14 +22,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "video": { + "battr": [ + 1, + 2, + 3 + ], "mimes": ["video/mp4"], "protocols": [2, 5], "w": 1024, @@ -37,7 +46,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/valid-extension.json b/adapters/brightroll/brightrolltest/exemplary/valid-extension.json index 970b4ade63a..da38c62be58 100644 --- a/adapters/brightroll/brightrolltest/exemplary/valid-extension.json +++ b/adapters/brightroll/brightrolltest/exemplary/valid-extension.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -22,14 +22,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-id", "video": { + "battr": [ + 1, + 2, + 3 + ], "mimes": ["video/mp4"], "protocols": [2, 5], "w": 1024, @@ -37,7 +46,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json b/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json index 9f24a471b31..d3295e5bffd 100644 --- a/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json +++ b/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json @@ -12,7 +12,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -25,7 +25,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } @@ -34,14 +34,23 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom", + "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", "body": { "id": "test-request-id", "at":1, + "bcat": [ + "IAB8-5", + "IAB8-18" + ], "imp": [ { "id": "test-imp-video-id", "video": { + "battr": [ + 1, + 2, + 3 + ], "mimes": ["video/mp4"], "protocols": [2, 5], "w": 1024, @@ -49,7 +58,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, @@ -62,7 +71,7 @@ }, "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } } diff --git a/adapters/brightroll/brightrolltest/params/race/banner.json b/adapters/brightroll/brightrolltest/params/race/banner.json index 91517e36fd8..6eb4ec6a337 100644 --- a/adapters/brightroll/brightrolltest/params/race/banner.json +++ b/adapters/brightroll/brightrolltest/params/race/banner.json @@ -1,3 +1,3 @@ { - "publisher": "cafemom" + "publisher": "adthrive" } diff --git a/adapters/brightroll/brightrolltest/params/race/video.json b/adapters/brightroll/brightrolltest/params/race/video.json index 91517e36fd8..6eb4ec6a337 100644 --- a/adapters/brightroll/brightrolltest/params/race/video.json +++ b/adapters/brightroll/brightrolltest/params/race/video.json @@ -1,3 +1,3 @@ { - "publisher": "cafemom" + "publisher": "adthrive" } diff --git a/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json b/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json index a6362c40adb..01beec712c7 100644 --- a/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json +++ b/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json @@ -3,7 +3,7 @@ "id": "test-request-id", "ext": { "bidder": { - "publisher": "cafemom" + "publisher": "adthrive" } } }, diff --git a/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json b/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json new file mode 100644 index 00000000000..da48108af0b --- /dev/null +++ b/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-missing-req-param-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisher":"test" + } + } + + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid publisher", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index d056de664b7..a160e87aad7 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -117,7 +117,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAvocet: avocet.NewAvocetAdapter(cfg.Adapters[string(openrtb_ext.BidderAvocet)].Endpoint), openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo), openrtb_ext.BidderBeintoo: beintoo.NewBeintooBidder(cfg.Adapters[string(openrtb_ext.BidderBeintoo)].Endpoint), - openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), + openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBrightroll)].ExtraAdapterInfo), openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), From 480d2a22042597a3179c3504b39e54d7a715fe00 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 8 Sep 2020 17:14:52 -0400 Subject: [PATCH 192/603] Refactor TCF 1/2 Vendor List Fetcher Tests (#1441) --- gdpr/gdpr.go | 9 +- gdpr/impl_test.go | 183 +++--- gdpr/vendorlist-fetching.go | 118 ++-- gdpr/vendorlist-fetching_test.go | 954 ++++++++++++++++++++++--------- 4 files changed, 851 insertions(+), 413 deletions(-) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 04db8cb92ed..6d447beb438 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -29,9 +29,10 @@ type Permissions interface { AMPException() bool } +// Versions of the GDPR TCF technical specification. const ( - tCF1 uint8 = 1 - tCF2 uint8 = 2 + tcf1SpecVersion uint8 = 1 + tcf2SpecVersion uint8 = 2 ) // NewPermissions gets an instance of the Permissions for use elsewhere in the project. @@ -45,8 +46,8 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ cfg: cfg, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tCF1), - tCF2: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tCF2)}, + tcf1SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf1SpecVersion), + tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf2SpecVersion)}, } } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 053e87536ab..d5114454f06 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -23,8 +23,8 @@ func TestNoConsentButAllowByDefault(t *testing.T) { }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: failedListFetcher, - tCF2: failedListFetcher, + tcf1SpecVersion: failedListFetcher, + tcf2SpecVersion: failedListFetcher, }, } allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") @@ -43,8 +43,8 @@ func TestNoConsentAndRejectByDefault(t *testing.T) { }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: failedListFetcher, - tCF2: failedListFetcher, + tcf1SpecVersion: failedListFetcher, + tcf2SpecVersion: failedListFetcher, }, } allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") @@ -56,12 +56,11 @@ func TestNoConsentAndRejectByDefault(t *testing.T) { } func TestAllowedSyncs(t *testing.T) { - vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ - 2: { - purposes: []int{1}, - }, - 3: { - purposes: []int{1}, + vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{ + {ID: 2, Purposes: []int{1}}, + {ID: 3, Purposes: []int{1}}, }, }) perms := permissionsImpl{ @@ -73,10 +72,10 @@ func TestAllowedSyncs(t *testing.T) { openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), }, @@ -92,12 +91,11 @@ func TestAllowedSyncs(t *testing.T) { } func TestProhibitedPurposes(t *testing.T) { - vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ - 2: { - purposes: []int{1}, // cookie reads/writes - }, - 3: { - purposes: []int{3}, // ad personalization + vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{ + {ID: 2, Purposes: []int{1}}, // cookie reads/writes + {ID: 3, Purposes: []int{3}}, // ad personalization }, }) perms := permissionsImpl{ @@ -109,10 +107,10 @@ func TestProhibitedPurposes(t *testing.T) { openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), }, @@ -128,12 +126,11 @@ func TestProhibitedPurposes(t *testing.T) { } func TestProhibitedVendors(t *testing.T) { - vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ - 2: { - purposes: []int{1}, // cookie reads/writes - }, - 3: { - purposes: []int{3}, // ad personalization + vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{ + {ID: 2, Purposes: []int{1}}, // cookie reads/writes + {ID: 3, Purposes: []int{3}}, // ad personalization }, }) perms := permissionsImpl{ @@ -145,10 +142,10 @@ func TestProhibitedVendors(t *testing.T) { openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), }, @@ -169,8 +166,8 @@ func TestMalformedConsent(t *testing.T) { HostVendorID: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: listFetcher(nil), - tCF2: listFetcher(nil), + tcf1SpecVersion: listFetcher(nil), + tcf2SpecVersion: listFetcher(nil), }, } @@ -180,12 +177,11 @@ func TestMalformedConsent(t *testing.T) { } func TestAllowPersonalInfo(t *testing.T) { - vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{ - 2: { - purposes: []int{1}, // cookie reads/writes - }, - 3: { - purposes: []int{1, 3}, // ad personalization + vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{ + {ID: 2, Purposes: []int{1}}, // cookie reads/writes + {ID: 3, Purposes: []int{1, 3}}, // ad personalization }, }) perms := permissionsImpl{ @@ -197,10 +193,10 @@ func TestAllowPersonalInfo(t *testing.T) { openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 1: parseVendorListData(t, vendorListData), }), }, @@ -222,24 +218,39 @@ func TestAllowPersonalInfo(t *testing.T) { assertBoolsEqual(t, true, allowPI) } -var tcf2BasicPurposes = map[uint16]*purposes{ - 2: {purposes: []int{1}}, //cookie reads/writes - 6: {purposes: []int{1, 2, 4}}, // ad personalization - 8: {purposes: []int{1, 7}}, - 10: {purposes: []int{2, 4, 7}}, - 32: {purposes: []int{1, 2, 4, 7}}, -} -var tcf2LegitInterests = map[uint16]*purposes{ - 6: {purposes: []int{7}}, - 8: {purposes: []int{2, 4}}, -} -var tcf2SpecialPuproses = map[uint16]*purposes{ - 6: {purposes: []int{1}}, - 10: {purposes: []int{1}}, -} -var tcf2FlexPurposes = map[uint16]*purposes{ - 6: {purposes: []int{1, 2, 4, 7}}, +func buildTCF2VendorList34() tcf2VendorList { + return tcf2VendorList{ + VendorListVersion: 2, + Vendors: map[string]*tcf2Vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, + "6": { + ID: 6, + Purposes: []int{1, 2, 4}, + LegIntPurposes: []int{7}, + SpecialPurposes: []int{1}, + FlexiblePurposes: []int{1, 2, 4, 7}, + }, + "8": { + ID: 8, + Purposes: []int{1, 7}, + LegIntPurposes: []int{2, 4}, + }, + "10": { + ID: 10, + Purposes: []int{2, 4, 7}, + SpecialPurposes: []int{1}, + }, + "32": { + ID: 32, + Purposes: []int{1, 2, 4, 7}, + }, + }, + } } + var tcf2Config = config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ @@ -261,7 +272,7 @@ type tcf2TestDef struct { } func TestAllowPersonalInfoTCF2(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -270,8 +281,8 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, @@ -316,7 +327,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { } func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -325,8 +336,8 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, @@ -338,11 +349,10 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { assert.EqualValuesf(t, true, allowPI, "AllowPI failure") assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") assert.EqualValuesf(t, true, allowID, "AllowID failure") - } func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -351,8 +361,8 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 15: parseVendorListDataV2(t, vendorListData), }), }, @@ -397,7 +407,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { } func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -406,8 +416,8 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, @@ -453,7 +463,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { } func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -462,8 +472,8 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, @@ -510,7 +520,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { } func TestAllowSyncTCF2(t *testing.T) { - vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -519,8 +529,8 @@ func TestAllowSyncTCF2(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, @@ -537,9 +547,9 @@ func TestAllowSyncTCF2(t *testing.T) { } func TestProhibitedPurposeSyncTCF2(t *testing.T) { - basicPurposes := tcf2BasicPurposes - basicPurposes[8] = &purposes{purposes: []int{7}} - vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + tcf2VendorList34 := buildTCF2VendorList34() + tcf2VendorList34.Vendors["8"].Purposes = []int{7} + vendorListData := tcf2MarshalVendorList(tcf2VendorList34) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -548,15 +558,15 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, } perms.cfg.HostVendorID = 8 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -567,9 +577,7 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { } func TestProhibitedVendorSyncTCF2(t *testing.T) { - basicPurposes := tcf2BasicPurposes - basicPurposes[10] = &purposes{purposes: []int{1}} - vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses) + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -579,20 +587,21 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { openrtb_ext.BidderOpenx: 10, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tCF1: nil, - tCF2: listFetcher(map[uint16]vendorlist.VendorList{ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, } perms.cfg.HostVendorID = 10 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 4, 6 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + // Permission disallowed due to consent string not including vendor 10. + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 1442f81c3ba..66a3f4ad2d6 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -26,67 +26,83 @@ type saveVendors func(uint16, api.VendorList) // // Nothing in this file is exported. Public APIs can be found in gdpr.go -func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - var fallbackVL api.VendorList = nil - - if TCFVer == tCF1 && len(cfg.TCF1.FallbackGVLPath) > 0 { - fallbackVL = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) - } - - // If we are not going to try fetching the GVL dynamically, we have a simple fetcher - if !cfg.TCF1.FetchGVL && TCFVer == tCF1 && fallbackVL != nil { - return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - return fallbackVL, nil +func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, tcfSpecVersion uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { + var fallback api.VendorList + if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) > 0 { + fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) + } + + // If we are not going to try fetching the GVL dynamically, we have a simple fetcher. + if !cfg.TCF1.FetchGVL && tcfSpecVersion == tcf1SpecVersion { + if fallback != nil { + return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { + return fallback, nil + } + } + return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { + return nil, makeVendorListNotFoundError(vendorListVersion) } } - // These save and load functions can be used to store & retrieve lists from our cache. - save, load := newVendorListCache(fallbackVL) - withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) - defer cancel() - populateCache(withTimeout, client, urlMaker, save, TCFVer) + cacheSave, cacheLoad := newVendorListCache(fallback) - saveOneSometimes := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), TCFVer) + preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) + defer cancel() + preloadCache(preloadContext, client, urlMaker, cacheSave, tcfSpecVersion) - return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - list := load(id) - if list != nil { + saveOneRateLimited := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), tcfSpecVersion) + return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { + // Attempt To Load From Cache + if list := cacheLoad(vendorListVersion); list != nil { return list, nil } - saveOneSometimes(ctx, client, urlMaker(id, TCFVer), save) - list = load(id) - if list != nil { + + // Attempt To Download + // - May not add to cache immediately. + saveOneRateLimited(ctx, client, urlMaker(vendorListVersion, tcfSpecVersion), cacheSave) + + // Attempt To Load From Cache Again + // - May have been added by the call to saveOneRateLimited. + if list := cacheLoad(vendorListVersion); list != nil { return list, nil } - if fallbackVL != nil { - return fallbackVL, nil + + // Attempt To Use Hardcoded Fallback + if fallback != nil { + return fallback, nil } - return nil, fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", id) + + // Give Up + return nil, makeVendorListNotFoundError(vendorListVersion) } } -// populateCache saves all the known versions of the vendor list for future use. -func populateCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, TCFVer uint8) { - latestVersion := saveOne(ctx, client, urlMaker(0, TCFVer), saver, TCFVer) +func makeVendorListNotFoundError(vendorListVersion uint16) error { + return fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", vendorListVersion) +} + +// preloadCache saves all the known versions of the vendor list for future use. +func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, tcfSpecVersion uint8) { + latestVersion := saveOne(ctx, client, urlMaker(0, tcfSpecVersion), saver, tcfSpecVersion) for i := uint16(1); i < latestVersion; i++ { - saveOne(ctx, client, urlMaker(i, TCFVer), saver, TCFVer) + saveOne(ctx, client, urlMaker(i, tcfSpecVersion), saver, tcfSpecVersion) } } // Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0, // this will fetch the latest version. -func vendorListURLMaker(version uint16, TCFVer uint8) string { - if TCFVer == 2 { - if version == 0 { +func vendorListURLMaker(vendorListVersion uint16, tcfSpecVersion uint8) string { + if tcfSpecVersion == tcf2SpecVersion { + if vendorListVersion == 0 { return "https://vendorlist.consensu.org/v2/vendor-list.json" } - return "https://vendorlist.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(version)) + ".json" + return "https://vendorlist.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" } - if version == 0 { + if vendorListVersion == 0 { return "https://vendorlist.consensu.org/vendorlist.json" } - return "https://vendorlist.consensu.org/v-" + strconv.Itoa(int(version)) + "/vendorlist.json" + return "https://vendorlist.consensu.org/v-" + strconv.Itoa(int(vendorListVersion)) + "/vendorlist.json" } // newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes. @@ -94,22 +110,24 @@ func vendorListURLMaker(version uint16, TCFVer uint8) string { // The goal here is to update quickly when new versions of the VendorList are released, but not wreck // server performance if a bad CMP starts sending us malformed consent strings that advertize a version // that doesn't exist yet. -func newOccasionalSaver(timeout time.Duration, TCFVer uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { +func newOccasionalSaver(timeout time.Duration, tcfSpecVersion uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { lastSaved := &atomic.Value{} lastSaved.Store(time.Time{}) return func(ctx context.Context, client *http.Client, url string, saver saveVendors) { now := time.Now() - if now.Sub(lastSaved.Load().(time.Time)).Minutes() > 10 { + timeSinceLastSave := now.Sub(lastSaved.Load().(time.Time)) + + if timeSinceLastSave.Minutes() > 10 { withTimeout, cancel := context.WithTimeout(ctx, timeout) defer cancel() - saveOne(withTimeout, client, url, saver, TCFVer) + saveOne(withTimeout, client, url, saver, tcfSpecVersion) lastSaved.Store(now) } } } -func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, cTFVer uint8) uint16 { +func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, tcfSpecVersion uint8) uint16 { req, err := http.NewRequest("GET", url, nil) if err != nil { glog.Errorf("Failed to build GET %s request. Cookie syncs may be affected: %v", url, err) @@ -133,7 +151,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return 0 } var newList api.VendorList - if cTFVer == 2 { + if tcfSpecVersion == tcf2SpecVersion { newList, err = vendorlist2.ParseEagerly(respBody) } else { newList, err = vendorlist.ParseEagerly(respBody) @@ -147,14 +165,15 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return newList.Version() } -func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) { +func newVendorListCache(fallbackVL api.VendorList) (save func(vendorListVersion uint16, list api.VendorList), load func(vendorListVersion uint16) api.VendorList) { cache := &sync.Map{} - save = func(id uint16, list api.VendorList) { - cache.Store(id, list) + save = func(vendorListVersion uint16, list api.VendorList) { + cache.Store(vendorListVersion, list) } - load = func(id uint16) api.VendorList { - list, ok := cache.Load(id) + + load = func(vendorListVersion uint16) api.VendorList { + list, ok := cache.Load(vendorListVersion) if ok { return list.(vendorlist.VendorList) } @@ -164,13 +183,14 @@ func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list ap } func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList { - fallbackVLbody, err := ioutil.ReadFile(fallbackGVLPath) + fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) if err != nil { glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) } - fallbackVL, err := vendorlist.ParseEagerly(fallbackVLbody) + + fallback, err := vendorlist.ParseEagerly(fallbackContents) if err != nil { glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) } - return fallbackVL + return fallback } diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 484a0a54b41..e5ad8793b4f 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -7,233 +7,697 @@ import ( "net/http/httptest" "strconv" "testing" - "time" "github.com/stretchr/testify/assert" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/config" ) -func TestVendorFetch(t *testing.T) { - vendorListOne := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, - }, - }) - vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2, 3}, +func TestTCF1FetcherInitialLoad(t *testing.T) { + // Loads two vendor lists during initialization by setting the latest vendor list version to 2. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 2, + vendorLists: map[int]string{ + 1: tcf1VendorList1, + 2: tcf1VendorList2, }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ - 1: vendorListOne, - 2: vendorListTwo, }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) - list, err := fetcher(context.Background(), 1) - assertNilErr(t, err) - vendor := list.Vendor(32) - assertBoolsEqual(t, true, vendor.Purpose(1)) - assertBoolsEqual(t, false, vendor.Purpose(3)) - assertBoolsEqual(t, false, vendor.Purpose(4)) - - list, err = fetcher(context.Background(), 2) - assertNilErr(t, err) - vendor = list.Vendor(32) - assertBoolsEqual(t, true, vendor.Purpose(1)) - assertBoolsEqual(t, true, vendor.Purpose(3)) -} - -func TestLazyFetch(t *testing.T) { - firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, - }, - }) - secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ - 3: { - purposes: []int{1}, - }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ - 1: firstVendorList, - 2: secondVendorList, + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "Fetch - Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 1, + }, + expected: vendorListFallbackExpected, + }, + { + description: "No Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorListFallbackExpected, + }, + { + description: "No Fetch - No Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 1, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + { + description: "No Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + } + + for _, test := range testCases { + runTest(t, test, tcf1SpecVersion, server) + } +} + +func TestTCF2FetcherInitialLoad(t *testing.T) { + // Loads two vendor lists during initialization by setting the latest vendor list version to 2. + // Ensures TCF1 fetch settings have no effect on TCF2. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 2, + vendorLists: map[int]string{ + 1: tcf2VendorList1, + 2: tcf2VendorList2, + }, }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) - list, err := fetcher(context.Background(), 2) - assertNilErr(t, err) + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "Fetch - Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "No Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - No Fallback - Vendor List 1", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 1, + }, + expected: vendorList1Expected, + }, + { + description: "No Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + } - vendor := list.Vendor(3) - assertBoolsEqual(t, true, vendor.Purpose(1)) - assertBoolsEqual(t, false, vendor.Purpose(2)) + for _, test := range testCases { + runTest(t, test, tcf2SpecVersion, server) + } } -func TestInitialTimeout(t *testing.T) { - list := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, +func TestTCF1FetcherDynamicLoadListExists(t *testing.T) { + // Loads the first vendor list during initialization by setting the latest vendor list version to 1. + // All other vendor lists will be dynamically loaded. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf1VendorList1, + 2: tcf1VendorList2, }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ - 1: list, }))) defer server.Close() - ctx, cancel := context.WithDeadline(context.Background(), time.Time{}) - defer cancel() - fetcher := newVendorListFetcher(ctx, testConfig(), server.Client(), testURLMaker(server), 1) - _, err := fetcher(context.Background(), 1) // This should do a lazy fetch, even though the initial call failed - assertNilErr(t, err) + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorListFallbackExpected, + }, + { + description: "No Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + } + + for _, test := range testCases { + runTest(t, test, tcf1SpecVersion, server) + } } -func TestFetchThrottling(t *testing.T) { - vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, - }, - }) - vendorListThree := mockVendorListData(t, 3, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, +func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { + // Loads the first vendor list during initialization by setting the latest vendor list version to 1. + // All other vendor lists will be dynamically loaded. + // Ensures TCF1 fetch settings have no effect on TCF2. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf2VendorList1, + 2: tcf2VendorList2, }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ - 1: "{}", - 2: vendorListTwo, - 3: vendorListThree, }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) - _, err := fetcher(context.Background(), 2) - assertNilErr(t, err) - _, err = fetcher(context.Background(), 3) - assertErr(t, err, false) + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + { + description: "No Fetch - No Fallback - Vendor List 2", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: vendorList2Expected, + }, + } + + for _, test := range testCases { + runTest(t, test, tcf2SpecVersion, server) + } } -func TestMalformedVendorlistFetch(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{1: "{}"}))) +func TestTCF1FetcherDynamicLoadListDoesntExist(t *testing.T) { + // Loads the first vendor list during initialization by setting the latest vendor list version to 1. + // All other vendor list load attempts will be done dynamically. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf1VendorList1, + }, + }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) - _, err := fetcher(context.Background(), 1) - assertErr(t, err, false) + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + { + description: "Fetch - Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorListFallbackExpected, + }, + { + description: "No Fetch - Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: vendorListFallbackExpected, + }, + { + description: "No Fetch - No Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + } + + for _, test := range testCases { + runTest(t, test, 1, server) + } } -func TestMissingVendorlistFetch(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{1: "{}"}))) +func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { + // Loads the first vendor list during initialization by setting the latest vendor list version to 1. + // All other vendor list load attempts will be done dynamically. + // Ensures TCF1 fetch settings have no effect on TCF2. + + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf2VendorList1, + }, + }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), 1) - _, err := fetcher(context.Background(), 2) - assertErr(t, err, false) -} + testCases := []test{ + { + description: "Fetch - No Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + { + description: "Fetch - Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: true, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + { + description: "No Fetch - Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: true, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + { + description: "No Fetch - No Fallback - Vendor Doesn't Exist", + setup: testSetup{ + enableTCF1Fetch: false, + enableTCF1Fallback: false, + vendorListVersion: 2, + }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", + }, + }, + } -func TestVendorListMaker(t *testing.T) { - assertStringsEqual(t, "https://vendorlist.consensu.org/vendorlist.json", vendorListURLMaker(0, 1)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v-2/vendorlist.json", vendorListURLMaker(2, 1)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v-12/vendorlist.json", vendorListURLMaker(12, 1)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v2/vendor-list.json", vendorListURLMaker(0, 2)) - assertStringsEqual(t, "https://vendorlist.consensu.org/v2/archives/vendor-list-v7.json", vendorListURLMaker(7, 2)) + for _, test := range testCases { + runTest(t, test, tcf2SpecVersion, server) + } } -func TestDefaultVendorList(t *testing.T) { - firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, - }, - }) - secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ - 12: { - purposes: []int{2}, +func TestTCF1FetcherThrottling(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1}}}, + }), + 2: tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 2, + Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1, 2}}}, + }), + 3: tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 3, + Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1, 2, 3}}}, + }), }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{ - 1: firstVendorList, - 2: secondVendorList, }))) defer server.Close() - testcfg := testConfig() - testcfg.TCF1.FetchGVL = true - testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) - list, err := fetcher(context.Background(), 12) - assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) - assert.Equal(t, uint16(215), list.Version(), "Expected to fetch default version 215, got %d", list.Version()) + // Dynamically Load List 2 Successfully + _, errList1 := fetcher(context.Background(), 2) + assert.NoError(t, errList1) - // Testing that we got the default vendorlist data, and not the version off the server. - vendor := list.Vendor(12) - assert.Equal(t, true, vendor.Purpose(1)) - assert.Equal(t, false, vendor.Purpose(2)) + // Fail To Load List 3 Due To Rate Limiting + // - The request is rate limited after dynamically list 2. + _, errList2 := fetcher(context.Background(), 3) + assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestFallbackVendorListPassthrough(t *testing.T) { - firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, +func TestTCF2FetcherThrottling(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: tcf2MarshalVendorList(tcf2VendorList{ + VendorListVersion: 1, + Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1}}}, + }), + 2: tcf2MarshalVendorList(tcf2VendorList{ + VendorListVersion: 2, + Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, + }), + 3: tcf2MarshalVendorList(tcf2VendorList{ + VendorListVersion: 3, + Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, + }), }, - }) - secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ - 12: { - purposes: []int{2}, + }))) + defer server.Close() + + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + + // Dynamically Load List 2 Successfully + _, errList1 := fetcher(context.Background(), 2) + assert.NoError(t, errList1) + + // Fail To Load List 3 Due To Rate Limiting + // - The request is rate limited after dynamically list 2. + _, errList2 := fetcher(context.Background(), 3) + assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") +} + +func TestTCF1MalformedVendorlist(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: "malformed", }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ - 1: firstVendorList, - 2: secondVendorList, }))) defer server.Close() - testcfg := testConfig() - testcfg.TCF1.FetchGVL = true - testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) - list, err := fetcher(context.Background(), 2) - assert.NoError(t, err, "Error with fetching existing vendorlist: %v", err) - assert.Equal(t, uint16(2), list.Version(), "Expected to fetch mock list version 2, got version %d", list.Version()) - - // Testing that we got the testing vendorlist data, and not the default. - vendor := list.Vendor(12) - assert.Equal(t, false, vendor.Purpose(1)) - assert.Equal(t, true, vendor.Purpose(2)) -} - -func TestFallbackVendorListNoFetch(t *testing.T) { - firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{ - 32: { - purposes: []int{1, 2}, - }, - }) - secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{ - 12: { - purposes: []int{2}, - }, - }) - server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{ - 1: firstVendorList, - 2: secondVendorList, + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) + _, err := fetcher(context.Background(), 1) + + // Fetching should fail since vendor list could not be unmarshalled. + assert.Error(t, err) +} + +func TestTCF2MalformedVendorlist(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ + vendorListLatestVersion: 1, + vendorLists: map[int]string{ + 1: "malformed", + }, }))) defer server.Close() - testcfg := testConfig() - testcfg.TCF1.FetchGVL = false - testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1) - list, err := fetcher(context.Background(), 2) - assert.NoError(t, err, "Error with fetching default vendorlist: %v", err) - assert.Equal(t, uint16(215), list.Version(), "Expected to fetch default version 215, got %d", list.Version()) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + _, err := fetcher(context.Background(), 1) + + // Fetching should fail since vendor list could not be unmarshalled. + assert.Error(t, err) +} + +func TestTCF1ServerUrlInvalid(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + server.Close() + + invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" } - // Testing that we got the default vendorlist data, and not the version off the server. - vendor := list.Vendor(12) - assert.Equal(t, true, vendor.Purpose(1)) - assert.Equal(t, false, vendor.Purpose(2)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf1SpecVersion) + _, err := fetcher(context.Background(), 1) + assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") +} + +func TestTCF2ServerUrlInvalid(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + server.Close() + + invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" } + + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf2SpecVersion) + _, err := fetcher(context.Background(), 1) + + assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") +} + +func TestTCF1ServerUnavailable(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + server.Close() + + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) + _, err := fetcher(context.Background(), 1) + + assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") +} + +func TestTCF2ServerUnavailable(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + server.Close() + + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + _, err := fetcher(context.Background(), 1) + + assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") +} + +func TestVendorListURLMaker(t *testing.T) { + testCases := []struct { + description string + tcfSpecVersion uint8 + vendorListVersion uint16 + expectedURL string + }{ + { + description: "TCF1 - Latest", + tcfSpecVersion: 1, + vendorListVersion: 0, // Forces latest version. + expectedURL: "https://vendorlist.consensu.org/vendorlist.json", + }, + { + description: "TCF1 - Specific", + tcfSpecVersion: 1, + vendorListVersion: 42, + expectedURL: "https://vendorlist.consensu.org/v-42/vendorlist.json", + }, + { + description: "TCF2 - Latest", + tcfSpecVersion: 2, + vendorListVersion: 0, // Forces latest version. + expectedURL: "https://vendorlist.consensu.org/v2/vendor-list.json", + }, + { + description: "TCF2 - Specific", + tcfSpecVersion: 2, + vendorListVersion: 42, + expectedURL: "https://vendorlist.consensu.org/v2/archives/vendor-list-v42.json", + }, + } + + for _, test := range testCases { + result := vendorListURLMaker(test.vendorListVersion, test.tcfSpecVersion) + assert.Equal(t, test.expectedURL, result) + } +} + +var tcf1VendorList1 = tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 1, + Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2}}}, +}) + +var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{ + VendorListVersion: 1, + Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}}, +}) + +var vendorList1Expected = testExpected{ + vendorListVersion: 1, + vendorID: 12, + vendorPurposes: map[int]bool{1: false, 2: true, 3: false}, +} + +var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{ + VendorListVersion: 2, + Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}}, +}) + +var tcf2VendorList2 = tcf2MarshalVendorList(tcf2VendorList{ + VendorListVersion: 2, + Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, +}) + +var vendorList2Expected = testExpected{ + vendorListVersion: 2, + vendorID: 12, + vendorPurposes: map[int]bool{1: false, 2: true, 3: true}, +} + +var vendorListFallbackExpected = testExpected{ + vendorListVersion: 215, // Values from hardcoded fallback file. + vendorID: 12, + vendorPurposes: map[int]bool{1: true, 2: false, 3: true}, +} + +type tcf1VendorList struct { + VendorListVersion uint16 `json:"vendorListVersion"` + Vendors []tcf1Vendor `json:"vendors"` +} + +type tcf1Vendor struct { + ID uint16 `json:"id"` + Purposes []int `json:"purposeIds"` +} + +func tcf1MarshalVendorList(vendorList tcf1VendorList) string { + json, _ := json.Marshal(vendorList) + return string(json) +} + +type tcf2VendorList struct { + VendorListVersion uint16 `json:"vendorListVersion"` + Vendors map[string]*tcf2Vendor `json:"vendors"` +} + +type tcf2Vendor struct { + ID uint16 `json:"id"` + Purposes []int `json:"purposes"` + LegIntPurposes []int `json:"legIntPurposes"` + FlexiblePurposes []int `json:"flexiblePurposes"` + SpecialPurposes []int `json:"specialPurposes"` +} + +func tcf2MarshalVendorList(vendorList tcf2VendorList) string { + json, _ := json.Marshal(vendorList) + return string(json) +} + +type serverSettings struct { + vendorListLatestVersion int + vendorLists map[int]string } // mockServer returns a handler which returns the given response for each global vendor list version. @@ -247,129 +711,74 @@ func TestFallbackVendorListNoFetch(t *testing.T) { // // If the "version" query param points to a version which doesn't exist, it returns a 403. // Don't ask why... that's just what the official page is doing. See https://vendorlist.consensu.org/v-9999/vendorlist.json -func mockServer(latestVersion int, responses map[int]string) func(http.ResponseWriter, *http.Request) { +func mockServer(settings serverSettings) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { - version := req.URL.Query().Get("version") - versionInt, err := strconv.Atoi(version) + vendorListVersion := req.URL.Query().Get("version") + vendorListVersionInt, err := strconv.Atoi(vendorListVersion) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Request had invalid version: " + version)) + w.Write([]byte("Request had invalid version: " + vendorListVersion)) return } - if versionInt == 0 { - versionInt = latestVersion + if vendorListVersionInt == 0 { + vendorListVersionInt = settings.vendorListLatestVersion } - response, ok := responses[versionInt] + response, ok := settings.vendorLists[vendorListVersionInt] if !ok { w.WriteHeader(http.StatusForbidden) - w.Write([]byte("Version not found: " + version)) + w.Write([]byte("Version not found: " + vendorListVersion)) return } w.Write([]byte(response)) } } -func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purposes) string { - type vendorContract struct { - ID uint16 `json:"id"` - Purposes []int `json:"purposeIds"` - } - - type vendorListContract struct { - Version uint16 `json:"vendorListVersion"` - Vendors []vendorContract `json:"vendors"` - } - - buildVendors := func(input map[uint16]*purposes) []vendorContract { - vendors := make([]vendorContract, 0, len(input)) - for id, purpose := range input { - vendors = append(vendors, vendorContract{ - ID: id, - Purposes: purpose.purposes, - }) - } - return vendors - } - - obj := vendorListContract{ - Version: version, - Vendors: buildVendors(vendors), - } - data, err := json.Marshal(obj) - assertNilErr(t, err) - return string(data) +type test struct { + description string + setup testSetup + expected testExpected } -type purposeMap map[uint16]*purposes - -func mockVendorListDataTCF2(t *testing.T, version uint16, basicPurposes purposeMap, legitInterests purposeMap, flexPurposes purposeMap, specialPurposes purposeMap) string { - type vendorContract struct { - ID uint16 `json:"id"` - Purposes []int `json:"purposes"` - LegIntPurposes []int `json:"legIntPurposes"` - FlexiblePurposes []int `json:"flexiblePurposes"` - SpecialPurposes []int `json:"specialPurposes"` - } - - type vendorListContract struct { - Version uint16 `json:"vendorListVersion"` - Vendors map[string]vendorContract `json:"vendors"` - } - - vendors := make(map[string]vendorContract, len(basicPurposes)) - for id, purpose := range basicPurposes { - sid := strconv.Itoa(int(id)) - vendor, ok := vendors[sid] - if !ok { - vendor = vendorContract{ID: id} - } - vendor.Purposes = purpose.purposes - vendors[sid] = vendor - } +type testSetup struct { + enableTCF1Fetch bool + enableTCF1Fallback bool + vendorListVersion uint16 +} - for id, purpose := range legitInterests { - sid := strconv.Itoa(int(id)) - vendor, ok := vendors[sid] - if !ok { - vendor = vendorContract{ID: id} - } - vendor.LegIntPurposes = purpose.purposes - vendors[sid] = vendor - } +type testExpected struct { + errorMessage string + vendorListVersion uint16 + vendorID uint16 + vendorPurposes map[int]bool +} - for id, purpose := range flexPurposes { - sid := strconv.Itoa(int(id)) - vendor, ok := vendors[sid] - if !ok { - vendor = vendorContract{ID: id} - } - vendor.FlexiblePurposes = purpose.purposes - vendors[sid] = vendor +func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Server) { + config := testConfig() + config.TCF1.FetchGVL = test.setup.enableTCF1Fetch + if test.setup.enableTCF1Fallback { + config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" } - for id, purpose := range specialPurposes { - sid := strconv.Itoa(int(id)) - vendor, ok := vendors[sid] - if !ok { - vendor = vendorContract{ID: id} + fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server), tcfSpecVersion) + vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) + + if test.expected.errorMessage != "" { + assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") + } else { + assert.NoError(t, err, test.description+":vendorlist") + assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") + vendor := vendorList.Vendor(test.expected.vendorID) + for id, expected := range test.expected.vendorPurposes { + result := vendor.Purpose(consentconstants.Purpose(id)) + assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) } - vendor.SpecialPurposes = purpose.purposes - vendors[sid] = vendor } - - obj := vendorListContract{ - Version: version, - Vendors: vendors, - } - data, err := json.Marshal(obj) - assertNilErr(t, err) - return string(data) } func testURLMaker(server *httptest.Server) func(uint16, uint8) string { url := server.URL - return func(version uint16, TCFVer uint8) string { - return url + "?version=" + strconv.Itoa(int(version)) + return func(vendorListVersion uint16, tcfSpecVersion uint8) string { + return url + "?version=" + strconv.Itoa(int(vendorListVersion)) } } @@ -379,9 +788,8 @@ func testConfig() config.GDPR { InitVendorlistFetch: 60 * 1000, ActiveVendorlistFetch: 1000 * 5, }, + TCF1: config.TCF1{ + FetchGVL: true, + }, } } - -type purposes struct { - purposes []int -} From 420da24edd3a6316e669ae969dcba10edb57fcaa Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Wed, 9 Sep 2020 12:01:33 -0700 Subject: [PATCH 193/603] Add validation checker for PRs and merges with github actions (#1476) --- .github/workflows/validate.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 00000000000..d7bb50fbabf --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,28 @@ +on: + push: + branches: + - master + pull_request: + release: + types: + - created +name: Validate +jobs: + Go: + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x] + os: [ubuntu-18.04] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Validate + run: | + ./validate.sh --nofmt --cov --race 10 + env: + GO111MODULE: "on" From 22c454c9e1d2617b109b1577c172a82da6a1358e Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Thu, 10 Sep 2020 10:17:40 -0700 Subject: [PATCH 194/603] Cache refactor (#1431) Reason: Cache has Fetcher-like functionality to handle both requests and imps at a time. Internally, it still uses two caches configured and searched separately, causing some code repetition. Reusing this code to cache other objects like accounts is not easy. Keeping the req/imp repetition in fetcher and out of cache allows for a reusable simpler cache, preserving existing fetcher functionality. Changes in this set: Cache is now a simple generic id->RawMessage store fetcherWithCache handles the separate req and imp caches ComposedCache handles single caches - but it does not appear to be used Removed cache overlap tests since they do not apply now Slightly less code --- stored_requests/caches/cachestest/reliable.go | 65 ++---------- stored_requests/caches/memory/cache.go | 60 ++++------- stored_requests/caches/memory/cache_test.go | 44 +++------ stored_requests/caches/nil_cache/nil_cache.go | 9 +- stored_requests/config/config.go | 7 +- stored_requests/config/config_test.go | 8 +- stored_requests/events/api/api_test.go | 31 +++--- stored_requests/events/events.go | 6 +- stored_requests/events/events_test.go | 21 ++-- stored_requests/fetcher.go | 57 ++++++----- stored_requests/fetcher_test.go | 99 ++++++++++--------- 11 files changed, 169 insertions(+), 238 deletions(-) diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go index e08a20e9cdb..7fbaf7238af 100644 --- a/stored_requests/caches/cachestest/reliable.go +++ b/stored_requests/caches/cachestest/reliable.go @@ -11,8 +11,6 @@ import ( const ( reqCacheKey = "known-req" reqCacheVal = `{"req":true}` - impCacheKey = "known-imp" - impCacheVal = `{"imp":true}` ) // AssertCacheRobustness runs tests which can be used to validate any Cache that is 100% reliable. @@ -20,84 +18,41 @@ const ( // // The cacheSupplier should be a function which returns a new Cache (with no data inside) on every call. // This will be called from separate Goroutines to make sure that different tests don't conflict. -func AssertCacheRobustness(t *testing.T, cacheSupplier func() stored_requests.Cache) { +func AssertCacheRobustness(t *testing.T, cacheSupplier func() stored_requests.CacheJSON) { t.Run("TestCacheMiss", cacheMissTester(cacheSupplier())) t.Run("TestCacheHit", cacheHitTester(cacheSupplier())) - t.Run("TestCacheMixed", cacheMixedTester(cacheSupplier())) - t.Run("TestCacheOverlap", cacheOverlapTester(cacheSupplier())) t.Run("TestCacheSaveInvalidate", cacheSaveInvalidateTester(cacheSupplier())) } -func cacheMissTester(cache stored_requests.Cache) func(*testing.T) { +func cacheMissTester(cache stored_requests.CacheJSON) func(*testing.T) { return func(t *testing.T) { - storedReqs, storedImps := cache.Get(context.Background(), []string{"unknown"}, nil) - assertMapLength(t, 0, storedReqs) - assertMapLength(t, 0, storedImps) + storedData := cache.Get(context.Background(), []string{"unknown"}) + assertMapLength(t, 0, storedData) } } -func cacheHitTester(cache stored_requests.Cache) func(*testing.T) { +func cacheHitTester(cache stored_requests.CacheJSON) func(*testing.T) { return func(t *testing.T) { cache.Save(context.Background(), map[string]json.RawMessage{ reqCacheKey: json.RawMessage(reqCacheVal), - }, map[string]json.RawMessage{ - impCacheKey: json.RawMessage(impCacheVal), }) - reqData, impData := cache.Get(context.Background(), []string{reqCacheKey}, []string{impCacheKey}) - if len(reqData) != 1 { - t.Errorf("The cache should have returned the data.") - } + reqData := cache.Get(context.Background(), []string{reqCacheKey}) assertMapLength(t, 1, reqData) assertHasValue(t, reqData, reqCacheKey, reqCacheVal) - - assertMapLength(t, 1, impData) - assertHasValue(t, impData, impCacheKey, impCacheVal) - } -} - -func cacheMixedTester(cache stored_requests.Cache) func(*testing.T) { - return func(t *testing.T) { - cache.Save(context.Background(), map[string]json.RawMessage{ - reqCacheKey: json.RawMessage(reqCacheVal), - }, nil) - reqData, impData := cache.Get(context.Background(), []string{reqCacheKey, "unknown-req"}, nil) - assertMapLength(t, 1, reqData) - assertHasValue(t, reqData, reqCacheKey, reqCacheVal) - assertMapLength(t, 0, impData) } } -func cacheOverlapTester(cache stored_requests.Cache) func(*testing.T) { - commonKey := "id" +func cacheSaveInvalidateTester(cache stored_requests.CacheJSON) func(*testing.T) { return func(t *testing.T) { cache.Save(context.Background(), map[string]json.RawMessage{ - commonKey: json.RawMessage(reqCacheVal), - }, map[string]json.RawMessage{ - commonKey: json.RawMessage(impCacheVal), - }) - reqData, impData := cache.Get(context.Background(), []string{commonKey}, []string{commonKey}) - assertMapLength(t, 1, reqData) - assertHasValue(t, reqData, commonKey, reqCacheVal) - assertMapLength(t, 1, impData) - assertHasValue(t, impData, commonKey, impCacheVal) - } -} - -func cacheSaveInvalidateTester(cache stored_requests.Cache) func(*testing.T) { - return func(t *testing.T) { - cache.Save(context.Background(), map[string]json.RawMessage{ - reqCacheKey: json.RawMessage(reqCacheVal), - }, map[string]json.RawMessage{ reqCacheKey: json.RawMessage(reqCacheVal), }) - reqData, impData := cache.Get(context.Background(), []string{reqCacheKey}, []string{reqCacheKey}) + reqData := cache.Get(context.Background(), []string{reqCacheKey}) assertMapLength(t, 1, reqData) - assertMapLength(t, 1, impData) - cache.Invalidate(context.Background(), []string{reqCacheKey}, []string{reqCacheKey}) - reqData, impData = cache.Get(context.Background(), []string{reqCacheKey}, []string{reqCacheKey}) + cache.Invalidate(context.Background(), []string{reqCacheKey}) + reqData = cache.Get(context.Background(), []string{reqCacheKey}) assertMapLength(t, 0, reqData) - assertMapLength(t, 0, impData) } } diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go index c5702080ef9..aea087e6d19 100644 --- a/stored_requests/caches/memory/cache.go +++ b/stored_requests/caches/memory/cache.go @@ -7,7 +7,6 @@ import ( "github.com/coocood/freecache" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" ) @@ -17,68 +16,51 @@ import ( // 2. The cache is too large. This will cause the least recently used items to be evicted. // // For no TTL, use ttlSeconds <= 0 -func NewCache(cfg *config.InMemoryCache) stored_requests.Cache { - return &cache{ - requestDataCache: newCacheForWithLimits(cfg.RequestCacheSize, cfg.TTL, "Request"), - impDataCache: newCacheForWithLimits(cfg.ImpCacheSize, cfg.TTL, "Imp"), - } -} - -func newCacheForWithLimits(size int, ttl int, dataType string) mapLike { +func NewCache(size int, ttl int, dataType string) stored_requests.CacheJSON { if ttl > 0 && size <= 0 { - glog.Fatal("No in-memory caches defined with a finite TTL but unbounded size. Config validation should have caught this. Failing fast because something is buggy.") + glog.Fatalf("No in-memory %s caches defined with a finite TTL but unbounded size. Config validation should have caught this. Failing fast because something is buggy.", dataType) } if size > 0 { glog.Infof("Using a Stored %s in-memory cache. Max size: %d bytes. TTL: %d seconds.", dataType, size, ttl) - return &pbsLRUCache{ - Cache: freecache.NewCache(size), - ttlSeconds: ttl, + return &cache{ + dataType: dataType, + cache: &pbsLRUCache{ + Cache: freecache.NewCache(size), + ttlSeconds: ttl, + }, } } else { glog.Infof("Using an unbounded Stored %s in-memory cache.", dataType) - return &pbsSyncMap{&sync.Map{}} + return &cache{ + dataType: dataType, + cache: &pbsSyncMap{&sync.Map{}}, + } } } type cache struct { - requestDataCache mapLike - impDataCache mapLike + dataType string + cache mapLike } -func (c *cache) Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage) { - requestData = doGet(c.requestDataCache, requestIDs) - impData = doGet(c.impDataCache, impIDs) - return -} - -func doGet(cache mapLike, ids []string) (data map[string]json.RawMessage) { +func (c *cache) Get(ctx context.Context, ids []string) (data map[string]json.RawMessage) { data = make(map[string]json.RawMessage, len(ids)) for _, id := range ids { - if val, ok := cache.Get(id); ok { + if val, ok := c.cache.Get(id); ok { data[id] = val } } return } -func (c *cache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) { - c.doSave(c.requestDataCache, storedRequests) - c.doSave(c.impDataCache, storedImps) -} - -func (c *cache) doSave(cache mapLike, values map[string]json.RawMessage) { - for id, data := range values { - cache.Set(id, data) +func (c *cache) Save(ctx context.Context, data map[string]json.RawMessage) { + for id, data := range data { + c.cache.Set(id, data) } } -func (c *cache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) { - doInvalidate(c.requestDataCache, requestIDs) - doInvalidate(c.impDataCache, impIDs) -} - -func doInvalidate(cache mapLike, ids []string) { +func (c *cache) Invalidate(ctx context.Context, ids []string) { for _, id := range ids { - cache.Delete(id) + c.cache.Delete(id) } } diff --git a/stored_requests/caches/memory/cache_test.go b/stored_requests/caches/memory/cache_test.go index ba4703ef89a..b89bd5af26f 100644 --- a/stored_requests/caches/memory/cache_test.go +++ b/stored_requests/caches/memory/cache_test.go @@ -7,52 +7,34 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/caches/cachestest" ) func TestLRURobustness(t *testing.T) { - cachestest.AssertCacheRobustness(t, func() stored_requests.Cache { - return NewCache(&config.InMemoryCache{ - RequestCacheSize: 256 * 1024, - ImpCacheSize: 256 * 1024, - TTL: -1, - }) + cachestest.AssertCacheRobustness(t, func() stored_requests.CacheJSON { + return NewCache(256*1024, -1, "TestData") }) } func TestUnboundedRobustness(t *testing.T) { - cachestest.AssertCacheRobustness(t, func() stored_requests.Cache { - return NewCache(&config.InMemoryCache{ - RequestCacheSize: 0, - ImpCacheSize: 0, - TTL: -1, - }) + cachestest.AssertCacheRobustness(t, func() stored_requests.CacheJSON { + return NewCache(0, -1, "TestData") }) } func TestRaceLRUConcurrency(t *testing.T) { - cache := NewCache(&config.InMemoryCache{ - RequestCacheSize: 256 * 1024, - ImpCacheSize: 256 * 1024, - TTL: -1, - }) - + cache := NewCache(256*1024, -1, "TestData") doRaceTest(t, cache) } func TestRaceUnboundedConcurrency(t *testing.T) { - cache := NewCache(&config.InMemoryCache{ - RequestCacheSize: 0, - ImpCacheSize: 0, - TTL: -1, - }) + cache := NewCache(0, -1, "TestData") doRaceTest(t, cache) } -func doRaceTest(t *testing.T, cache stored_requests.Cache) { +func doRaceTest(t *testing.T, cache stored_requests.CacheJSON) { done := make(chan struct{}) sets := [][]int{rand.Perm(100), rand.Perm(100), rand.Perm(100)} @@ -70,26 +52,26 @@ func doRaceTest(t *testing.T, cache stored_requests.Cache) { } } -func readLots(cache stored_requests.Cache, done chan<- struct{}, reads []int) { +func readLots(cache stored_requests.CacheJSON, done chan<- struct{}, reads []int) { var s struct{} for _, i := range reads { - cache.Get(context.Background(), sliceForVal(i), sliceForVal(-i)) + cache.Get(context.Background(), sliceForVal(i)) } done <- s } -func writeLots(cache stored_requests.Cache, done chan<- struct{}, writes []int) { +func writeLots(cache stored_requests.CacheJSON, done chan<- struct{}, writes []int) { var s struct{} for _, i := range writes { - cache.Save(context.Background(), mapForVal(i), mapForVal(-i)) + cache.Save(context.Background(), mapForVal(i)) } done <- s } -func invalidateLots(cache stored_requests.Cache, done chan<- struct{}, invalidates []int) { +func invalidateLots(cache stored_requests.CacheJSON, done chan<- struct{}, invalidates []int) { var s struct{} for _, i := range invalidates { - cache.Invalidate(context.Background(), sliceForVal(i), sliceForVal(-i)) + cache.Invalidate(context.Background(), sliceForVal(i)) } done <- s } diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go index de29156e3c9..d043ae55c96 100644 --- a/stored_requests/caches/nil_cache/nil_cache.go +++ b/stored_requests/caches/nil_cache/nil_cache.go @@ -8,13 +8,14 @@ import ( // NilCache is a no-op cache which does nothing useful. type NilCache struct{} -func (c *NilCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage) { - return nil, nil +func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMessage { + return make(map[string]json.RawMessage) } -func (c *NilCache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) { + +func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) { return } -func (c *NilCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) { +func (c *NilCache) Invalidate(ctx context.Context, ids []string) { return } diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index e81d9667a73..6c663cbf0a2 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -178,10 +178,13 @@ func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fe func newCache(cfg *config.StoredRequests) stored_requests.Cache { if cfg.InMemoryCache.Type == "none" { glog.Infof("No Stored %s cache configured. The %s Fetcher backend will be used for all data requests", cfg.DataType(), cfg.DataType()) - return &nil_cache.NilCache{} + return stored_requests.Cache{&nil_cache.NilCache{}, &nil_cache.NilCache{}} } - return memory.NewCache(&cfg.InMemoryCache) + return stored_requests.Cache{ + Requests: memory.NewCache(cfg.InMemoryCache.RequestCacheSize, cfg.InMemoryCache.TTL, "Requests"), + Imps: memory.NewCache(cfg.InMemoryCache.ImpCacheSize, cfg.InMemoryCache.TTL, "Imps"), + } } func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) { diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index 4c3943ea5be..af6f4a4f514 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -102,8 +102,8 @@ func TestNewHTTPEvents(t *testing.T) { func TestNewEmptyCache(t *testing.T) { cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}}) - cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil) - reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil) + cache.Requests.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}) + reqs := cache.Requests.Get(context.Background(), []string{"foo"}) if len(reqs) != 0 { t.Errorf("The newCache method should return an empty cache if the config asks for it.") } @@ -117,8 +117,8 @@ func TestNewInMemoryCache(t *testing.T) { ImpCacheSize: 100, }, }) - cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil) - reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil) + cache.Requests.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}) + reqs := cache.Requests.Get(context.Background(), []string{"foo"}) if len(reqs) != 1 { t.Errorf("The newCache method should return an in-memory cache if the config asks for it.") } diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go index 64cf68b0a91..d2db4557573 100644 --- a/stored_requests/events/api/api_test.go +++ b/stored_requests/events/api/api_test.go @@ -9,22 +9,21 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/caches/memory" "github.com/prebid/prebid-server/stored_requests/events" ) func TestGoodRequests(t *testing.T) { - cache := memory.NewCache(&config.InMemoryCache{ - RequestCacheSize: 256 * 1024, - ImpCacheSize: 256 * 1024, - TTL: -1, - }) - + cache := stored_requests.Cache{ + Requests: memory.NewCache(256*1024, -1, "Requests"), + Imps: memory.NewCache(256*1024, -1, "Imps"), + } id := "1" config := fmt.Sprintf(`{"id": "%s"}`, id) initialValue := map[string]json.RawMessage{id: json.RawMessage(config)} - cache.Save(context.Background(), initialValue, initialValue) + cache.Requests.Save(context.Background(), initialValue) + cache.Imps.Save(context.Background(), initialValue) apiEvents, endpoint := NewEventsAPI() @@ -51,7 +50,8 @@ func TestGoodRequests(t *testing.T) { } <-updateOccurred - reqData, impData := cache.Get(context.Background(), []string{id}, []string{id}) + reqData := cache.Requests.Get(context.Background(), []string{id}) + impData := cache.Imps.Get(context.Background(), []string{id}) assertHasValue(t, reqData, id, config) assertHasValue(t, impData, id, config) @@ -66,18 +66,17 @@ func TestGoodRequests(t *testing.T) { } <-invalidateOccurred - reqData, impData = cache.Get(context.Background(), []string{id}, []string{id}) + reqData = cache.Requests.Get(context.Background(), []string{id}) + impData = cache.Imps.Get(context.Background(), []string{id}) assertMapLength(t, 0, reqData) assertMapLength(t, 0, impData) } func TestBadRequests(t *testing.T) { - cache := memory.NewCache(&config.InMemoryCache{ - RequestCacheSize: 256 * 1024, - ImpCacheSize: 256 * 1024, - TTL: -1, - }) - + cache := stored_requests.Cache{ + Requests: memory.NewCache(256*1024, -1, "Requests"), + Imps: memory.NewCache(256*1024, -1, "Imps"), + } apiEvents, endpoint := NewEventsAPI() listener := events.SimpleEventListener() go listener.Listen(cache, apiEvents) diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index ea67e8eeeb9..ba08f13d65b 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -61,12 +61,14 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer for { select { case save := <-events.Saves(): - cache.Save(context.Background(), save.Requests, save.Imps) + cache.Requests.Save(context.Background(), save.Requests) + cache.Imps.Save(context.Background(), save.Imps) if e.onSave != nil { e.onSave() } case invalidation := <-events.Invalidations(): - cache.Invalidate(context.Background(), invalidation.Requests, invalidation.Imps) + cache.Requests.Invalidate(context.Background(), invalidation.Requests) + cache.Imps.Invalidate(context.Background(), invalidation.Imps) if e.onInvalidate != nil { e.onInvalidate() } diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index 240a697592a..74263d14b93 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/caches/memory" ) @@ -16,12 +16,10 @@ func TestListen(t *testing.T) { saves: make(chan Save), invalidations: make(chan Invalidation), } - - cache := memory.NewCache(&config.InMemoryCache{ - RequestCacheSize: 256 * 1024, - ImpCacheSize: 256 * 1024, - TTL: -1, - }) + cache := stored_requests.Cache{ + Requests: memory.NewCache(256*1024, -1, "Requests"), + Imps: memory.NewCache(256*1024, -1, "Imps"), + } // create channels to synchronize saveOccurred := make(chan struct{}) @@ -42,7 +40,8 @@ func TestListen(t *testing.T) { Requests: data, Imps: data, } - cache.Save(context.Background(), save.Requests, save.Imps) + cache.Requests.Save(context.Background(), save.Requests) + cache.Requests.Save(context.Background(), save.Imps) config = fmt.Sprintf(`{"id": "%s", "updated": true}`, id) data = map[string]json.RawMessage{id: json.RawMessage(config)} @@ -54,7 +53,8 @@ func TestListen(t *testing.T) { ep.saves <- save <-saveOccurred - requestData, impData := cache.Get(context.Background(), idSlice, idSlice) + requestData := cache.Requests.Get(context.Background(), idSlice) + impData := cache.Imps.Get(context.Background(), idSlice) if !reflect.DeepEqual(requestData, data) || !reflect.DeepEqual(impData, data) { t.Error("Update failed") } @@ -67,7 +67,8 @@ func TestListen(t *testing.T) { ep.invalidations <- invalidation <-invalidateOccurred - requestData, impData = cache.Get(context.Background(), idSlice, idSlice) + requestData = cache.Requests.Get(context.Background(), idSlice) + impData = cache.Imps.Get(context.Background(), idSlice) if len(requestData) > 0 || len(impData) > 0 { t.Error("Invalidate failed") } diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index a31b9989bd0..e9716e08a23 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -37,9 +37,9 @@ type CategoryFetcher interface { // AllFetcher is an interface that encapsulates both the original Fetcher and the CategoryFetcher type AllFetcher interface { - FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) - FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) - FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) + Fetcher + AccountFetcher + CategoryFetcher } // NotFoundError is an error type to flag that an ID was not found by the Fetcher. @@ -62,7 +62,11 @@ func (e NotFoundError) Error() string { // Cache is an intermediate layer which can be used to create more complex Fetchers by composition. // Implementations must be safe for concurrent access by multiple goroutines. // To add a Cache layer in front of a Fetcher, see WithCache() -type Cache interface { +type Cache struct { + Requests CacheJSON + Imps CacheJSON +} +type CacheJSON interface { // Get works much like Fetcher.FetchRequests, with a few exceptions: // // 1. Any (actionable) errors should be logged by the implementation, rather than returned. @@ -73,37 +77,33 @@ type Cache interface { // // Nil slices and empty strings are treated as "no ops". That is, a nil requestID will always produce a nil // "stored request data" in the response. - Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage) + Get(ctx context.Context, ids []string) (data map[string]json.RawMessage) // Invalidate will ensure that all values associated with the given IDs // are no longer returned by the cache until new values are saved via Update - Invalidate(ctx context.Context, requestIDs []string, impIDs []string) + Invalidate(ctx context.Context, ids []string) // Save will add or overwrite the data in the cache at the given keys - Save(ctx context.Context, requestData map[string]json.RawMessage, impData map[string]json.RawMessage) + Save(ctx context.Context, data map[string]json.RawMessage) } // ComposedCache creates an interface to treat a slice of caches as a single cache -type ComposedCache []Cache +type ComposedCache []CacheJSON // Get will attempt to Get from the caches in the order in which they are in the slice, // stopping as soon as a value is found (or when all caches have been exhausted) -func (c ComposedCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage) { - requestData = make(map[string]json.RawMessage, len(requestIDs)) - impData = make(map[string]json.RawMessage, len(impIDs)) +func (c ComposedCache) Get(ctx context.Context, ids []string) (data map[string]json.RawMessage) { + data = make(map[string]json.RawMessage, len(ids)) - remainingReqIDs := requestIDs - remainingImpIDs := impIDs + remainingIDs := ids for _, cache := range c { - cachedReqData, cachedImpData := cache.Get(ctx, remainingReqIDs, remainingImpIDs) - - requestData, remainingReqIDs = updateFromCache(requestData, remainingReqIDs, cachedReqData) - impData, remainingImpIDs = updateFromCache(impData, remainingImpIDs, cachedImpData) + cachedData := cache.Get(ctx, remainingIDs) + data, remainingIDs = updateFromCache(data, remainingIDs, cachedData) - // return if all ids filled - if len(remainingReqIDs) == 0 && len(remainingImpIDs) == 0 { - return + // finish early if all ids filled + if len(remainingIDs) == 0 { + break } } @@ -129,16 +129,16 @@ func updateFromCache(data map[string]json.RawMessage, ids []string, newData map[ } // Invalidate will propagate invalidations to all underlying caches -func (c ComposedCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) { +func (c ComposedCache) Invalidate(ctx context.Context, ids []string) { for _, cache := range c { - cache.Invalidate(ctx, requestIDs, impIDs) + cache.Invalidate(ctx, ids) } } // Save will propagate saves to all underlying caches -func (c ComposedCache) Save(ctx context.Context, requestData map[string]json.RawMessage, impData map[string]json.RawMessage) { +func (c ComposedCache) Save(ctx context.Context, data map[string]json.RawMessage) { for _, cache := range c { - cache.Save(ctx, requestData, impData) + cache.Save(ctx, data) } } @@ -148,7 +148,7 @@ type fetcherWithCache struct { metricsEngine pbsmetrics.MetricsEngine } -// WithCache returns a Fetcher which uses the given Cache before delegating to the original. +// WithCache returns a Fetcher which uses the given Caches before delegating to the original. // This can be called multiple times to compose Cache layers onto the backing Fetcher, though // it is usually more desirable to first compose caches with Compose, ensuring propagation of updates // and invalidations through all cache layers. @@ -161,7 +161,9 @@ func WithCache(fetcher AllFetcher, cache Cache, metricsEngine pbsmetrics.Metrics } func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { - requestData, impData = f.cache.Get(ctx, requestIDs, impIDs) + + requestData = f.cache.Requests.Get(ctx, requestIDs) + impData = f.cache.Imps.Get(ctx, impIDs) // Fixes #311 leftoverImps := findLeftovers(impIDs, impData) @@ -178,7 +180,8 @@ func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []strin fetcherReqData, fetcherImpData, fetcherErrs := f.fetcher.FetchRequests(ctx, leftoverReqs, leftoverImps) errs = fetcherErrs - f.cache.Save(ctx, fetcherReqData, fetcherImpData) + f.cache.Requests.Save(ctx, fetcherReqData) + f.cache.Imps.Save(ctx, fetcherImpData) requestData = mergeData(requestData, fetcherReqData) impData = mergeData(impData, fetcherImpData) diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go index 1928d1165db..396ba3d04b2 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -12,25 +12,27 @@ import ( "github.com/stretchr/testify/mock" ) -func setupFetcherWithCacheDeps() (*mockCache, *mockFetcher, AllFetcher, *pbsmetrics.MetricsEngineMock) { - cache := &mockCache{} +func setupFetcherWithCacheDeps() (*mockCache, *mockCache, *mockFetcher, AllFetcher, *pbsmetrics.MetricsEngineMock) { + reqCache := &mockCache{} + impCache := &mockCache{} metricsEngine := &pbsmetrics.MetricsEngineMock{} fetcher := &mockFetcher{} - afetcherWithCache := WithCache(fetcher, cache, metricsEngine) + afetcherWithCache := WithCache(fetcher, Cache{reqCache, impCache}, metricsEngine) - return cache, fetcher, afetcherWithCache, metricsEngine + return reqCache, impCache, fetcher, afetcherWithCache, metricsEngine } func TestPerfectCache(t *testing.T) { - cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() + reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() impIDs := []string{"known"} reqIDs := []string{"req-id"} ctx := context.Background() - cache.On("Get", ctx, reqIDs, impIDs).Return( + reqCache.On("Get", ctx, reqIDs).Return( map[string]json.RawMessage{ "req-id": json.RawMessage(`{"req":true}`), - }, + }) + impCache.On("Get", ctx, impIDs).Return( map[string]json.RawMessage{ "known": json.RawMessage(`{}`), }) @@ -41,7 +43,8 @@ func TestPerfectCache(t *testing.T) { reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs) - cache.AssertExpectations(t) + reqCache.AssertExpectations(t) + impCache.AssertExpectations(t) fetcher.AssertExpectations(t) metricsEngine.AssertExpectations(t) assert.JSONEq(t, `{"req":true}`, string(reqData["req-id"]), "Fetch requests should fetch the right request data") @@ -50,15 +53,16 @@ func TestPerfectCache(t *testing.T) { } func TestImperfectCache(t *testing.T) { - cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() + reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() impIDs := []string{"cached", "uncached"} ctx := context.Background() - cache.On("Get", ctx, []string(nil), impIDs).Return( - map[string]json.RawMessage{}, + impCache.On("Get", ctx, impIDs).Return( map[string]json.RawMessage{ "cached": json.RawMessage(`true`), }) + reqCache.On("Get", ctx, []string(nil)).Return( + map[string]json.RawMessage{}) fetcher.On("FetchRequests", ctx, []string{}, []string{"uncached"}).Return( map[string]json.RawMessage{}, @@ -67,11 +71,11 @@ func TestImperfectCache(t *testing.T) { }, []error{}, ) - cache.On("Save", ctx, - map[string]json.RawMessage{}, + impCache.On("Save", ctx, map[string]json.RawMessage{ "uncached": json.RawMessage(`false`), }) + reqCache.On("Save", ctx, map[string]json.RawMessage{}) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0) metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 1) @@ -79,7 +83,7 @@ func TestImperfectCache(t *testing.T) { reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs) - cache.AssertExpectations(t) + impCache.AssertExpectations(t) fetcher.AssertExpectations(t) metricsEngine.AssertExpectations(t) assert.Len(t, reqData, 0, "Fetch requests should return nil if no request IDs were passed") @@ -89,14 +93,15 @@ func TestImperfectCache(t *testing.T) { } func TestMissingData(t *testing.T) { - cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() + reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() impIDs := []string{"unknown"} ctx := context.Background() - cache.On("Get", ctx, []string(nil), impIDs).Return( - map[string]json.RawMessage{}, + impCache.On("Get", ctx, impIDs).Return( map[string]json.RawMessage{}, ) + reqCache.On("Get", ctx, []string(nil)).Return( + map[string]json.RawMessage{}) fetcher.On("FetchRequests", ctx, []string{}, impIDs).Return( map[string]json.RawMessage{}, map[string]json.RawMessage{}, @@ -104,8 +109,10 @@ func TestMissingData(t *testing.T) { errors.New("Data not found"), }, ) - cache.On("Save", ctx, + impCache.On("Save", ctx, map[string]json.RawMessage{}, + ) + reqCache.On("Save", ctx, map[string]json.RawMessage{}, ) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0) @@ -115,7 +122,8 @@ func TestMissingData(t *testing.T) { reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs) - cache.AssertExpectations(t) + reqCache.AssertExpectations(t) + impCache.AssertExpectations(t) fetcher.AssertExpectations(t) metricsEngine.AssertExpectations(t) assert.Len(t, errs, 1, "FetchRequests for missing data should return an error") @@ -125,15 +133,16 @@ func TestMissingData(t *testing.T) { // Prevents #311 func TestCacheSaves(t *testing.T) { - cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() + reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() impIDs := []string{"abc", "abc"} ctx := context.Background() - cache.On("Get", ctx, []string(nil), impIDs).Return( - map[string]json.RawMessage{}, + impCache.On("Get", ctx, impIDs).Return( map[string]json.RawMessage{ "abc": json.RawMessage(`{}`), }) + reqCache.On("Get", ctx, []string(nil)).Return( + map[string]json.RawMessage{}) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0) metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 2) @@ -141,7 +150,7 @@ func TestCacheSaves(t *testing.T) { _, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, []string{"abc", "abc"}) - cache.AssertExpectations(t) + impCache.AssertExpectations(t) fetcher.AssertExpectations(t) metricsEngine.AssertExpectations(t) assert.Len(t, impData, 1, "FetchRequests should return data only once for duplicate requests") @@ -154,38 +163,34 @@ func TestComposedCache(t *testing.T) { c2 := &mockCache{} c3 := &mockCache{} c4 := &mockCache{} - cache := ComposedCache{c1, c2, c3, c4} + impCache := &mockCache{} + cache := Cache{ + Requests: ComposedCache{c1, c2, c3, c4}, + Imps: impCache, + } metricsEngine := &pbsmetrics.MetricsEngineMock{} fetcher := &mockFetcher{} aFetcherWithCache := WithCache(fetcher, cache, metricsEngine) - impIDs := []string{"1", "2", "3"} reqIDs := []string{"1", "2", "3"} + impIDs := []string{} ctx := context.Background() - c1.On("Get", ctx, reqIDs, impIDs).Return( - map[string]json.RawMessage{ - "1": json.RawMessage(`{"id": "1"}`), - }, + c1.On("Get", ctx, reqIDs).Return( map[string]json.RawMessage{ "1": json.RawMessage(`{"id": "1"}`), }) - c2.On("Get", ctx, []string{"2", "3"}, []string{"2", "3"}).Return( - map[string]json.RawMessage{ - "2": json.RawMessage(`{"id": "2"}`), - }, + c2.On("Get", ctx, []string{"2", "3"}).Return( map[string]json.RawMessage{ "2": json.RawMessage(`{"id": "2"}`), }) - c3.On("Get", ctx, []string{"3"}, []string{"3"}).Return( - map[string]json.RawMessage{ - "3": json.RawMessage(`{"id": "3"}`), - }, + c3.On("Get", ctx, []string{"3"}).Return( map[string]json.RawMessage{ "3": json.RawMessage(`{"id": "3"}`), }) + impCache.On("Get", ctx, []string{}).Return(map[string]json.RawMessage{}) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 3) metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0) - metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 3) + metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 0) metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheMiss, 0) reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs) @@ -193,14 +198,12 @@ func TestComposedCache(t *testing.T) { c1.AssertExpectations(t) c2.AssertExpectations(t) c3.AssertExpectations(t) + impCache.AssertExpectations(t) fetcher.AssertExpectations(t) metricsEngine.AssertExpectations(t) assert.Len(t, reqData, len(reqIDs), "FetchRequests should be able to return all request data from a composed cache") assert.Len(t, impData, len(impIDs), "FetchRequests should be able to return all imp data from a composed cache") assert.Len(t, errs, 0, "FetchRequests shouldn't return an error when trying to use a composed cache") - assert.JSONEq(t, `{"id": "1"}`, string(impData["1"]), "FetchRequests should fetch the right imp data") - assert.JSONEq(t, `{"id": "2"}`, string(impData["2"]), "FetchRequests should fetch the right imp data") - assert.JSONEq(t, `{"id": "3"}`, string(impData["3"]), "FetchRequests should fetch the right imp data") assert.JSONEq(t, `{"id": "1"}`, string(reqData["1"]), "FetchRequests should fetch the right req data") assert.JSONEq(t, `{"id": "2"}`, string(reqData["2"]), "FetchRequests should fetch the right req data") assert.JSONEq(t, `{"id": "3"}`, string(reqData["3"]), "FetchRequests should fetch the right req data") @@ -228,15 +231,15 @@ type mockCache struct { mock.Mock } -func (c *mockCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage) { - args := c.Called(ctx, requestIDs, impIDs) - return args.Get(0).(map[string]json.RawMessage), args.Get(1).(map[string]json.RawMessage) +func (c *mockCache) Get(ctx context.Context, ids []string) map[string]json.RawMessage { + args := c.Called(ctx, ids) + return args.Get(0).(map[string]json.RawMessage) } -func (c *mockCache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) { - c.Called(ctx, storedRequests, storedImps) +func (c *mockCache) Save(ctx context.Context, data map[string]json.RawMessage) { + c.Called(ctx, data) } -func (c *mockCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) { - c.Called(ctx, requestIDs, impIDs) +func (c *mockCache) Invalidate(ctx context.Context, ids []string) { + c.Called(ctx, ids) } From 42e676570cf64c244b08b48efdec4de7d4995ec1 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 10 Sep 2020 13:26:43 -0400 Subject: [PATCH 195/603] Pass Through First Party Context Data (#1479) --- endpoints/openrtb2/auction.go | 16 +- endpoints/openrtb2/auction_test.go | 95 +++++++++- ...valid-context-allowed-with-ext-bidder.json | 32 ++++ ...id-context-allowed-with-prebid-bidder.json | 36 ++++ ...rstpartydata-imp-ext-multiple-bidders.json | 173 +++++++++++++++++ ...ydata-imp-ext-multiple-prebid-bidders.json | 179 ++++++++++++++++++ .../firstpartydata-imp-ext-one-bidder.json | 103 ++++++++++ ...stpartydata-imp-ext-one-prebid-bidder.json | 108 +++++++++++ exchange/utils.go | 38 ++-- openrtb_ext/bidders.go | 3 + openrtb_ext/bidders_test.go | 5 + openrtb_ext/request.go | 4 + 12 files changed, 769 insertions(+), 23 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bc0cd90073f..41c1c1677a5 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -765,8 +765,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st } // Also accept bidder exts within imp[...].ext.prebid.bidder - // NOTE: This is not part of the official API, we are not expecting clients - // migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} + // NOTE: This is not part of the official API yet, so we are not expecting clients + // to migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} // at this time // https://github.com/prebid/prebid-server/pull/846#issuecomment-476352224 if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { @@ -785,7 +785,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st /* Process all the bidder exts in the request */ disabledBidders := []string{} for bidder, ext := range bidderExts { - if bidder != openrtb_ext.PrebidExtKey { + if isBidderToValidate(bidder) { coreBidder := bidder if tmp, isAlias := aliases[bidder]; isAlias { coreBidder = tmp @@ -820,12 +820,20 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st // TODO #713 Fix this here if len(bidderExts) < 1 { errL = append(errL, fmt.Errorf("request.imp[%d].ext must contain at least one bidder", impIndex)) - return errL } return errL } +func isBidderToValidate(bidder string) bool { + // PrebidExtKey is a special case for the prebid config section and is not considered a bidder. + + // FirstPartyDataContextExtKey is a special case for the first party data context section + // and is not considered a bidder. + + return bidder != openrtb_ext.PrebidExtKey && bidder != openrtb_ext.FirstPartyDataContextExtKey +} + func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { if len(ext) < 1 { return nil, nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 53fea2e0500..7dc244a28c3 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -141,6 +141,16 @@ func TestGoodRequests(t *testing.T) { supplementary.assert(t) } +func TestFirstPartyDataRequests(t *testing.T) { + validRequests := &getResponseFromDirectory{ + dir: "sample-requests/first-party-data", + payloadGetter: getRequestPayload, + messageGetter: nilReturner, + expectedCode: http.StatusOK, + } + validRequests.assert(t) +} + // TestGoodNativeRequests makes sure we return 200s on well-formed Native requests. func TestGoodNativeRequests(t *testing.T) { tests := &getResponseFromDirectory{ @@ -1127,10 +1137,73 @@ func TestDisabledBidder(t *testing.T) { } } -func TestValidateImpExtDisabledBidder(t *testing.T) { - imp := &openrtb.Imp{ - Ext: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`), +func TestValidateImpExt(t *testing.T) { + testCases := []struct { + description string + impExt json.RawMessage + expectedImpExt string + expectedErrs []error + }{ + { + description: "Empty", + impExt: nil, + expectedImpExt: "", + expectedErrs: []error{errors.New("request.imp[0].ext is required")}, + }, + { + description: "Valid Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), + expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`), + expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Prebid Ext Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, + expectedErrs: []error{}, + // request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder. + }, + { + description: "Valid Prebid Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + // request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder. + }, + { + description: "Valid Prebid Ext Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + // request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}. + }, + { + description: "Valid Prebid Ext Bidder + Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + // request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}. + }, } + deps := &endpointDeps{ &nobidExchange{}, newParamsValidator(t), @@ -1149,9 +1222,19 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, } - errs := deps.validateImpExt(imp, nil, 0) - assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext)) - assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs) + + for _, test := range testCases { + imp := &openrtb.Imp{Ext: test.impExt} + + errs := deps.validateImpExt(imp, nil, 0) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext)) + } else { + assert.Empty(t, imp.Ext) + } + assert.Equal(t, test.expectedErrs, errs) + } } func validRequest(t *testing.T, filename string) string { diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json new file mode 100644 index 00000000000..aa205fc55ce --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json @@ -0,0 +1,32 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json new file mode 100644 index 00000000000..1616e84b416 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json @@ -0,0 +1,36 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json new file mode 100644 index 00000000000..8004c3c2646 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json @@ -0,0 +1,173 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }, { + "seat": "rubicon", + "bid": [{ + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json new file mode 100644 index 00000000000..d62afccf426 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -0,0 +1,179 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }, { + "seat": "rubicon", + "bid": [{ + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json new file mode 100644 index 00000000000..6f0bab9529c --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json @@ -0,0 +1,103 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json new file mode 100644 index 00000000000..1610b9ea47e --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -0,0 +1,108 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index 2e9e4dc8f80..5863f6c8530 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -273,13 +273,18 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { imp := imps[i] impExt := impExts[i] + var firstPartyDataContext json.RawMessage + if context, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { + firstPartyDataContext = context + } + rawPrebidExt, ok := impExt[openrtb_ext.PrebidExtKey] if ok { var prebidExt openrtb_ext.ExtImpPrebid if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, &splitImps); errs != nil { + if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { errList = append(errList, errs...) } @@ -287,7 +292,7 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { } } - if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, &splitImps); errs != nil { + if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { errList = append(errList, errs...) } } @@ -295,35 +300,38 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { return splitImps, nil } -// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid" and bidder params exist. +// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid", "context", and bidder params exist. // It will not mutate the input imp. // This function will write the new imps to the output map passed in func sanitizedImpCopy(imp *openrtb.Imp, bidderExts map[string]json.RawMessage, rawPrebidExt json.RawMessage, + firstPartyDataContext json.RawMessage, out *map[string][]openrtb.Imp) []error { var prebidExt map[string]json.RawMessage var errs []error - // We don't want to include other demand partners' bidder params - // in the sanitized imp if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil { - delete(prebidExt, "bidder") - - var err error - if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { - errs = append(errs, err) + // Remove the entire bidder field. We will already have the content we need in bidderExts. We + // don't want to include other demand partners' bidder params in the sanitized imp. + if _, hasBidderField := prebidExt["bidder"]; hasBidderField { + delete(prebidExt, "bidder") + + var err error + if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { + errs = append(errs, err) + } } } for bidder, ext := range bidderExts { - if bidder == openrtb_ext.PrebidExtKey { + if bidder == openrtb_ext.PrebidExtKey || bidder == openrtb_ext.FirstPartyDataContextExtKey { continue } impCopy := *imp - newExt := make(map[string]json.RawMessage, 2) + newExt := make(map[string]json.RawMessage, 3) newExt["bidder"] = ext @@ -331,6 +339,10 @@ func sanitizedImpCopy(imp *openrtb.Imp, newExt[openrtb_ext.PrebidExtKey] = rawPrebidExt } + if len(firstPartyDataContext) > 0 { + newExt[openrtb_ext.FirstPartyDataContextExtKey] = firstPartyDataContext + } + rawExt, err := json.Marshal(newExt) if err != nil { errs = append(errs, err) @@ -392,7 +404,7 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN } // parseImpExts does a partial-unmarshal of the imp[].Ext field. -// The keys in the returned map are expected to be "prebid", core BidderNames, or Aliases for this request. +// The keys in the returned map are expected to be "prebid", "context", core BidderNames, or Aliases for this request. func parseImpExts(imps []openrtb.Imp) ([]map[string]json.RawMessage, error) { exts := make([]map[string]json.RawMessage, len(imps)) // Loop over every impression in the request diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 761f53d441e..876eeab86bd 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -20,6 +20,9 @@ type BidderName string // BidderNameGeneral is reserved for non-bidder specific messages when using a map keyed on the bidder name. const BidderNameGeneral = BidderName("general") +// BidderNameContext is reserved for first party data. +const BidderNameContext = BidderName("context") + // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project. // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions. // The bidder name 'general' is not allowed since it has special meaning in message maps. diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index d49b23237ed..9f05f526905 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -61,3 +61,8 @@ func TestBidderListDoesNotDefineGeneral(t *testing.T) { bidders := BidderList() assert.NotContains(t, bidders, BidderNameGeneral) } + +func TestBidderListDoesNotDefineContext(t *testing.T) { + bidders := BidderList() + assert.NotContains(t, bidders, BidderNameContext) +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index d6edf47f939..42ac9d9d4b9 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,6 +5,10 @@ import ( "errors" ) +// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved +// for first party data support. +const FirstPartyDataContextExtKey string = "context" + // ExtRequest defines the contract for bidrequest.ext type ExtRequest struct { Prebid ExtRequestPrebid `json:"prebid"` From fa23f5c226df99a9a4ef318100fdb7d84d3e40fa Mon Sep 17 00:00:00 2001 From: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Date: Thu, 10 Sep 2020 22:33:14 +0100 Subject: [PATCH 196/603] Added new size 640x360 (Id: 198) (#1490) --- adapters/rubicon/rubicon.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 56ae7b2f792..7d6e0e12039 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -236,6 +236,7 @@ var rubiSizeMap = map[rubiSize]int{ {w: 800, h: 250}: 125, {w: 200, h: 600}: 126, {w: 640, h: 320}: 156, + {w: 640, h: 360}: 198, } // defines the contract for bidrequest.user.ext.eids[i].ext From 65c6c3608d0cca20168a69c8cf14d043a0d39a45 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Mon, 14 Sep 2020 07:19:40 -0700 Subject: [PATCH 197/603] Refactor: move getAccount to accounts package (from openrtb2) (#1483) --- account/account.go | 69 +++++++++++++++++++++ account/account_test.go | 94 +++++++++++++++++++++++++++++ endpoints/openrtb2/amp_auction.go | 3 +- endpoints/openrtb2/auction.go | 56 +---------------- endpoints/openrtb2/auction_test.go | 70 +-------------------- endpoints/openrtb2/video_auction.go | 3 +- 6 files changed, 170 insertions(+), 125 deletions(-) create mode 100644 account/account.go create mode 100644 account/account_test.go diff --git a/account/account.go b/account/account.go new file mode 100644 index 00000000000..2f27b61efab --- /dev/null +++ b/account/account.go @@ -0,0 +1,69 @@ +package account + +import ( + "context" + "encoding/json" + "fmt" + + jsonpatch "github.com/evanphx/json-patch" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbsmetrics" + "github.com/prebid/prebid-server/stored_requests" +) + +// GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied +func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string) (account *config.Account, errs []error) { + // Check BlacklistedAcctMap until we have deprecated it + if _, found := cfg.BlacklistedAcctMap[accountID]; found { + return nil, []error{&errortypes.BlacklistedAcct{ + Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), + }} + } + if cfg.AccountRequired && accountID == pbsmetrics.PublisherUnknown { + return nil, []error{&errortypes.AcctRequired{ + Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), + }} + } + if accountJSON, accErrs := fetcher.FetchAccount(ctx, accountID); len(accErrs) > 0 || accountJSON == nil { + // accountID does not reference a valid account + for _, e := range accErrs { + if _, ok := e.(stored_requests.NotFoundError); !ok { + errs = append(errs, e) + } + } + if cfg.AccountRequired && cfg.AccountDefaults.Disabled { + errs = append(errs, &errortypes.AcctRequired{ + Message: fmt.Sprintf("Prebid-server could not verify the Account ID. Please reach out to the prebid server host."), + }) + return nil, errs + } + // Make a copy of AccountDefaults instead of taking a reference, + // to preserve original accountID in case is needed to check NonStandardPublisherMap + pubAccount := cfg.AccountDefaults + pubAccount.ID = accountID + account = &pubAccount + } else { + // accountID resolved to a valid account, merge with AccountDefaults for a complete config + account = &config.Account{} + completeJSON, err := jsonpatch.MergePatch(cfg.AccountDefaultsJSON(), accountJSON) + if err == nil { + err = json.Unmarshal(completeJSON, account) + } + if err != nil { + errs = append(errs, err) + return nil, errs + } + // Fill in ID if needed, so it can be left out of account definition + if len(account.ID) == 0 { + account.ID = accountID + } + } + if account.Disabled { + errs = append(errs, &errortypes.BlacklistedAcct{ + Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), + }) + return nil, errs + } + return account, nil +} diff --git a/account/account_test.go b/account/account_test.go new file mode 100644 index 00000000000..0d192f18510 --- /dev/null +++ b/account/account_test.go @@ -0,0 +1,94 @@ +package account + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbsmetrics" + "github.com/prebid/prebid-server/stored_requests" + "github.com/stretchr/testify/assert" +) + +var mockAccountData = map[string]json.RawMessage{ + "valid_acct": json.RawMessage(`{"disabled":false}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), +} + +type mockAccountFetcher struct { +} + +func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + if account, ok := mockAccountData[accountID]; ok { + return account, nil + } + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} +} + +func TestGetAccount(t *testing.T) { + unknown := pbsmetrics.PublisherUnknown + testCases := []struct { + accountID string + // account_required + required bool + // account_defaults.disabled + disabled bool + // expected error, or nil if account should be found + err error + }{ + // Blacklisted account is always rejected even in permissive setup + {accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, + + // empty pubID + {accountID: unknown, required: false, disabled: false, err: nil}, + {accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}}, + {accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, + + // pubID given but is not a valid host account (does not exist) + {accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil}, + {accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil}, + {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, + + // pubID given and matches a valid host account with Disabled: false + {accountID: "valid_acct", required: false, disabled: false, err: nil}, + {accountID: "valid_acct", required: true, disabled: false, err: nil}, + {accountID: "valid_acct", required: false, disabled: true, err: nil}, + {accountID: "valid_acct", required: true, disabled: true, err: nil}, + + // pubID given and matches a host account explicitly disabled (Disabled: true on account json) + {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, + } + + for _, test := range testCases { + description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled) + t.Run(description, func(t *testing.T) { + cfg := &config.Configuration{ + BlacklistedAcctMap: map[string]bool{"bad_acct": true}, + AccountRequired: test.required, + AccountDefaults: config.Account{Disabled: test.disabled}, + } + fetcher := &mockAccountFetcher{} + assert.NoError(t, cfg.MarshalAccountDefaults()) + + account, errors := GetAccount(context.Background(), cfg, fetcher, test.accountID) + + if test.err == nil { + assert.Empty(t, errors) + assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID") + assert.Equal(t, false, account.Disabled, "returned account must not be disabled") + } else { + assert.NotEmpty(t, errors, "expected errors but got success") + assert.Nil(t, account, "return account must be nil on error") + assert.IsType(t, test.err, errors[0], "error is of unexpected type") + } + }) + } +} diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 54f4706902d..1e92569e260 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -16,6 +16,7 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/mxmCherry/openrtb" + accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -158,7 +159,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } labels.PubID = getAccountID(req.Site.Publisher) // Look up account now that we have resolved the pubID value - account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID) if len(acctIDErrs) > 0 { errL = append(errL, acctIDErrs...) httpStatus := http.StatusBadRequest diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 41c1c1677a5..d6cbc2285fb 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -22,6 +22,7 @@ import ( "github.com/mxmCherry/openrtb" "github.com/mxmCherry/openrtb/native" nativeRequests "github.com/mxmCherry/openrtb/native/request" + accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -156,7 +157,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } // Look up account now that we have resolved the pubID value - account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID) if len(acctIDErrs) > 0 { errL = append(errL, acctIDErrs...) writeError(errL, w, &labels) @@ -1317,56 +1318,3 @@ func getAccountID(pub *openrtb.Publisher) string { } return pbsmetrics.PublisherUnknown } - -func (deps *endpointDeps) getAccount(ctx context.Context, pubID string) (account *config.Account, errs []error) { - // Check BlacklistedAcctMap until we have deprecated it - if _, found := deps.cfg.BlacklistedAcctMap[pubID]; found { - return nil, []error{&errortypes.BlacklistedAcct{ - Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID), - }} - } - if deps.cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown { - return nil, []error{&errortypes.AcctRequired{ - Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), - }} - } - if accountJSON, accErrs := deps.accounts.FetchAccount(ctx, pubID); len(accErrs) > 0 || accountJSON == nil { - // pubID does not reference a valid account - if len(accErrs) > 0 { - errs = append(errs, errs...) - } - if deps.cfg.AccountRequired && deps.cfg.AccountDefaults.Disabled { - errs = append(errs, &errortypes.AcctRequired{ - Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), - }) - return nil, errs - } - // Make a copy of AccountDefaults instead of taking a reference, - // to preserve original pubID in case is needed to check NonStandardPublisherMap - pubAccount := deps.cfg.AccountDefaults - pubAccount.ID = pubID - account = &pubAccount - } else { - // pubID resolved to a valid account, merge with AccountDefaults for a complete config - account = &config.Account{} - completeJSON, err := jsonpatch.MergePatch(deps.cfg.AccountDefaultsJSON(), accountJSON) - if err == nil { - err = json.Unmarshal(completeJSON, account) - } - if err != nil { - errs = append(errs, err) - return nil, errs - } - // Fill in ID if needed, so it can be left out of account definition - if len(account.ID) == 0 { - account.ID = pubID - } - } - if account.Disabled { - errs = append(errs, &errortypes.BlacklistedAcct{ - Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID), - }) - return nil, errs - } - return -} diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 7dc244a28c3..925cffcebeb 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1984,8 +1984,7 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s } var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), - "disabled_acct": json.RawMessage(`{"disabled":true}`), + "valid_acct": json.RawMessage(`{"disabled":false}`), } type mockAccountFetcher struct { @@ -2058,70 +2057,3 @@ type hardcodedResponseIPValidator struct { func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { return v.response } - -func TestGetAccount(t *testing.T) { - unknown := pbsmetrics.PublisherUnknown - testCases := []struct { - accountID string - // account_required - required bool - // account_defaults.disabled - disabled bool - // expected error, or nil if account should be found - err error - }{ - // Blacklisted account is always rejected even in permissive setup - {accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - - // empty pubID - {accountID: unknown, required: false, disabled: false, err: nil}, - {accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}}, - {accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, - - // pubID given but is not a valid host account (does not exist) - {accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil}, - {accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil}, - {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, - - // pubID given and matches a valid host account with Disabled: false - {accountID: "valid_acct", required: false, disabled: false, err: nil}, - {accountID: "valid_acct", required: true, disabled: false, err: nil}, - {accountID: "valid_acct", required: false, disabled: true, err: nil}, - {accountID: "valid_acct", required: true, disabled: true, err: nil}, - - // pubID given and matches a host account explicitly disabled (Disabled: true on account json) - {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, - } - - for _, test := range testCases { - description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled) - t.Run(description, func(t *testing.T) { - deps := &endpointDeps{ - cfg: &config.Configuration{ - BlacklistedAcctMap: map[string]bool{"bad_acct": true}, - AccountRequired: test.required, - AccountDefaults: config.Account{Disabled: test.disabled}, - }, - accounts: &mockAccountFetcher{}, - } - assert.NoError(t, deps.cfg.MarshalAccountDefaults()) - - account, errors := deps.getAccount(context.Background(), test.accountID) - - if test.err == nil { - assert.Empty(t, errors) - assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID") - assert.Equal(t, false, account.Disabled, "returned account must not be disabled") - } else { - assert.NotEmpty(t, errors, "expected errors but got success") - assert.Nil(t, account, "return account must be nil on error") - assert.IsType(t, test.err, errors[0], "error is of unexpected type") - } - }) - } -} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index f5494751cc2..ab5634c7853 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -23,6 +23,7 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/mxmCherry/openrtb" + accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/exchange" @@ -255,7 +256,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } // Look up account now that we have resolved the pubID value - account, acctIDErrs := deps.getAccount(ctx, labels.PubID) + account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID) if len(acctIDErrs) > 0 { handleError(&labels, w, acctIDErrs, &vo, &debugLog) return From e7d0babd32833e6fba144d020b4a09c0aacdeb50 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 15 Sep 2020 10:21:18 -0400 Subject: [PATCH 198/603] Fixed TCF2 Geo Only Enforcement (#1492) --- exchange/utils.go | 4 +- privacy/enforcement.go | 30 +++-- privacy/enforcement_test.go | 131 ++++++++++------------ privacy/scrubber.go | 52 +++++++-- privacy/scrubber_test.go | 218 +++++++++++++++--------------------- 5 files changed, 213 insertions(+), 222 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index 5863f6c8530..22b28adcacb 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -107,12 +107,10 @@ func cleanOpenRTBRequests(ctx context.Context, coreBidder := resolveBidder(bidder.String(), aliases) var publisherID = labels.PubID - ok, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) - privacyEnforcement.GDPR = !ok && err == nil + _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent) privacyEnforcement.GDPRGeo = !geo && err == nil privacyEnforcement.GDPRID = !id && err == nil } else { - privacyEnforcement.GDPR = false privacyEnforcement.GDPRGeo = false privacyEnforcement.GDPRID = false } diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 9c23c320680..3f157329cf6 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -8,7 +8,6 @@ import ( type Enforcement struct { CCPA bool COPPA bool - GDPR bool GDPRGeo bool GDPRID bool LMT bool @@ -16,7 +15,7 @@ type Enforcement struct { // Any returns true if at least one privacy policy requires enforcement. func (e Enforcement) Any() bool { - return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.GDPRID || e.LMT + return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT } // Apply cleans personally identifiable information from an OpenRTB bid request. @@ -26,17 +25,33 @@ func (e Enforcement) Apply(bidRequest *openrtb.BidRequest, ampGDPRException bool func (e Enforcement) apply(bidRequest *openrtb.BidRequest, ampGDPRException bool, scrubber Scrubber) { if bidRequest != nil && e.Any() { - bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) + bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(ampGDPRException), e.getGeoScrubStrategy()) } } +func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID { + if e.COPPA || e.GDPRID || e.CCPA || e.LMT { + return ScrubStrategyDeviceIDAll + } + + return ScrubStrategyDeviceIDNone +} + +func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 { + if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT { + return ScrubStrategyIPV4Lowest8 + } + + return ScrubStrategyIPV4None +} + func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { if e.COPPA { return ScrubStrategyIPV6Lowest32 } - if e.GDPR || e.CCPA || e.LMT { + if e.GDPRGeo || e.CCPA || e.LMT { return ScrubStrategyIPV6Lowest16 } @@ -60,12 +75,11 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs return ScrubStrategyUserIDAndDemographic } - if e.GDPR && ampGDPRException { - return ScrubStrategyUserNone + if e.CCPA || e.LMT { + return ScrubStrategyUserID } - // If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above) - if e.CCPA || e.GDPRID || e.LMT { + if e.GDPRID && !ampGDPRException { return ScrubStrategyUserID } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index ef02e28147a..0cf36a614c4 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -19,7 +19,6 @@ func TestAny(t *testing.T) { enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: false, @@ -31,7 +30,6 @@ func TestAny(t *testing.T) { enforcement: Enforcement{ CCPA: true, COPPA: true, - GDPR: true, GDPRGeo: true, GDPRID: true, LMT: true, @@ -43,7 +41,6 @@ func TestAny(t *testing.T) { enforcement: Enforcement{ CCPA: false, COPPA: true, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: true, @@ -63,6 +60,8 @@ func TestApply(t *testing.T) { description string enforcement Enforcement ampGDPRException bool + expectedDeviceID ScrubStrategyDeviceID + expectedDeviceIPv4 ScrubStrategyIPV4 expectedDeviceIPv6 ScrubStrategyIPV6 expectedDeviceGeo ScrubStrategyGeo expectedUser ScrubStrategyUser @@ -73,12 +72,12 @@ func TestApply(t *testing.T) { enforcement: Enforcement{ CCPA: true, COPPA: true, - GDPR: true, GDPRGeo: true, GDPRID: true, LMT: true, }, - ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, expectedUser: ScrubStrategyUserIDAndDemographic, @@ -89,12 +88,12 @@ func TestApply(t *testing.T) { enforcement: Enforcement{ CCPA: true, COPPA: false, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: false, }, - ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserID, @@ -105,124 +104,97 @@ func TestApply(t *testing.T) { enforcement: Enforcement{ CCPA: false, COPPA: true, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: false, }, - ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, expectedDeviceGeo: ScrubStrategyGeoFull, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoFull, }, { - description: "GDPR Only", + description: "GDPR Only - Full", enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: true, GDPRGeo: true, GDPRID: true, LMT: false, }, ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserID, expectedUserGeo: ScrubStrategyGeoReducedPrecision, }, { - description: "GDPR Only, ampGDPRException", + description: "GDPR Only - Full - AMP Exception", enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: true, GDPRGeo: true, GDPRID: true, LMT: false, }, ampGDPRException: true, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserNone, expectedUserGeo: ScrubStrategyGeoReducedPrecision, }, { - description: "CCPA Only, ampGDPRException", - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPR: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - ampGDPRException: true, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserID, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "COPPA and GDPR, ampGDPRException", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPR: true, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - ampGDPRException: true, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "GDPR Only, no Geo", + description: "GDPR Only - ID Only", enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: true, GDPRGeo: false, GDPRID: true, LMT: false, }, ampGDPRException: false, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4None, + expectedDeviceIPv6: ScrubStrategyIPV6None, expectedDeviceGeo: ScrubStrategyGeoNone, expectedUser: ScrubStrategyUserID, expectedUserGeo: ScrubStrategyGeoNone, }, { - description: "GDPR Only, Geo only", + description: "GDPR Only - ID Only - AMP Exception", enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: false, - GDPRGeo: true, - GDPRID: false, + GDPRGeo: false, + GDPRID: true, LMT: false, }, - ampGDPRException: false, + ampGDPRException: true, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4None, expectedDeviceIPv6: ScrubStrategyIPV6None, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, + expectedDeviceGeo: ScrubStrategyGeoNone, expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, + expectedUserGeo: ScrubStrategyGeoNone, }, { - description: "GDPR Only, ID exception", + description: "GDPR Only - Geo Only", enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: true, GDPRGeo: true, GDPRID: false, LMT: false, }, ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDNone, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserNone, @@ -233,32 +205,50 @@ func TestApply(t *testing.T) { enforcement: Enforcement{ CCPA: false, COPPA: false, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: true, }, - ampGDPRException: false, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserID, expectedUserGeo: ScrubStrategyGeoReducedPrecision, }, { - description: "LMT Only, ampGDPRException", + description: "Interactions: COPPA Only + AMP Exception", enforcement: Enforcement{ CCPA: false, - COPPA: false, - GDPR: false, + COPPA: true, GDPRGeo: false, GDPRID: false, - LMT: true, + LMT: false, }, ampGDPRException: true, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserID, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, + expectedDeviceGeo: ScrubStrategyGeoFull, + expectedUser: ScrubStrategyUserIDAndDemographic, + expectedUserGeo: ScrubStrategyGeoFull, + }, + { + description: "Interactions: COPPA + GDPR Full + AMP Exception", + enforcement: Enforcement{ + CCPA: false, + COPPA: true, + GDPRGeo: true, + GDPRID: true, + LMT: false, + }, + ampGDPRException: true, + expectedDeviceID: ScrubStrategyDeviceIDAll, + expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, + expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, + expectedDeviceGeo: ScrubStrategyGeoFull, + expectedUser: ScrubStrategyUserIDAndDemographic, + expectedUserGeo: ScrubStrategyGeoFull, }, } @@ -271,7 +261,7 @@ func TestApply(t *testing.T) { replacedUser := &openrtb.User{} m := &mockScrubber{} - m.On("ScrubDevice", req.Device, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() + m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once() test.enforcement.apply(req, test.ampGDPRException, m) @@ -290,7 +280,6 @@ func TestApplyNoneApplicable(t *testing.T) { enforcement := Enforcement{ CCPA: false, COPPA: false, - GDPR: false, GDPRGeo: false, GDPRID: false, LMT: false, @@ -315,8 +304,8 @@ type mockScrubber struct { mock.Mock } -func (m *mockScrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { - args := m.Called(device, ipv6, geo) +func (m *mockScrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { + args := m.Called(device, id, ipv4, ipv6, geo) return args.Get(0).(*openrtb.Device) } diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 655436838e6..8771c8b3282 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -7,6 +7,17 @@ import ( "github.com/mxmCherry/openrtb" ) +// ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. +type ScrubStrategyIPV4 int + +const ( + // ScrubStrategyIPV4None does not remove any part of an IPV4 address. + ScrubStrategyIPV4None ScrubStrategyIPV4 = iota + + // ScrubStrategyIPV4Lowest8 zeroes out the last 8 bits of an IPV4 address. + ScrubStrategyIPV4Lowest8 +) + // ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address. type ScrubStrategyIPV6 int @@ -49,9 +60,20 @@ const ( ScrubStrategyUserID ) +// ScrubStrategyDeviceID defines the approach to remove hardware id and device id data. +type ScrubStrategyDeviceID int + +const ( + // ScrubStrategyDeviceIDNone does not remove hardware id and device id data. + ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota + + // ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes) + ScrubStrategyDeviceIDAll +) + // Scrubber removes PII from parts of an OpenRTB request. type Scrubber interface { - ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device + ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User } @@ -62,20 +84,28 @@ func NewScrubber() Scrubber { return scrubber{} } -func (scrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { if device == nil { return nil } deviceCopy := *device - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" - deviceCopy.IP = scrubIPV4(device.IP) + + switch id { + case ScrubStrategyDeviceIDAll: + deviceCopy.DIDMD5 = "" + deviceCopy.DIDSHA1 = "" + deviceCopy.DPIDMD5 = "" + deviceCopy.DPIDSHA1 = "" + deviceCopy.IFA = "" + deviceCopy.MACMD5 = "" + deviceCopy.MACSHA1 = "" + } + + switch ipv4 { + case ScrubStrategyIPV4Lowest8: + deviceCopy.IP = scrubIPV4Lowest8(device.IP) + } switch ipv6 { case ScrubStrategyIPV6Lowest16: @@ -124,7 +154,7 @@ func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo Sc return &userCopy } -func scrubIPV4(ip string) string { +func scrubIPV4Lowest8(ip string) string { i := strings.LastIndex(ip, ".") if i == -1 { return "" diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 67241019317..e0a2cb86f64 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -31,28 +31,21 @@ func TestScrubDevice(t *testing.T) { testCases := []struct { description string expected *openrtb.Device + id ScrubStrategyDeviceID + ipv4 ScrubStrategyIPV4 ipv6 ScrubStrategyIPV6 geo ScrubStrategyGeo }{ { - description: "IPv6 Lowest 32 & Geo Full", - expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{}, - }, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoFull, + description: "All Strageties - None", + expected: device, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4None, + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoNone, }, { - description: "IPv6 Lowest 16 & Geo Full", + description: "All Strageties - Strictest", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -62,14 +55,16 @@ func TestScrubDevice(t *testing.T) { MACMD5: "", IFA: "", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", + IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", Geo: &openrtb.Geo{}, }, - ipv6: ScrubStrategyIPV6Lowest16, + id: ScrubStrategyDeviceIDAll, + ipv4: ScrubStrategyIPV4Lowest8, + ipv6: ScrubStrategyIPV6Lowest32, geo: ScrubStrategyGeoFull, }, { - description: "IPv6 None & Geo Full", + description: "Isolated - ID - All", expected: &openrtb.Device{ DIDMD5: "", DIDSHA1: "", @@ -78,161 +73,126 @@ func TestScrubDevice(t *testing.T) { MACSHA1: "", MACMD5: "", IFA: "", - IP: "1.2.3.0", + IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{}, + Geo: device.Geo, }, + id: ScrubStrategyDeviceIDAll, + ipv4: ScrubStrategyIPV4None, ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoFull, + geo: ScrubStrategyGeoNone, }, { - description: "IPv6 Lowest 32 & Geo Reduced", + description: "Isolated - IPv4 - Lowest 8", expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", + DIDMD5: "anyDIDMD5", + DIDSHA1: "anyDIDSHA1", + DPIDMD5: "anyDPIDMD5", + DPIDSHA1: "anyDPIDSHA1", + MACSHA1: "anyMACSHA1", + MACMD5: "anyMACMD5", + IFA: "anyIFA", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + Geo: device.Geo, }, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoReducedPrecision, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4Lowest8, + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoNone, }, { - description: "IPv6 Lowest 16 & Geo Reduced", + description: "Isolated - IPv6 - Lowest 16", expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", + DIDMD5: "anyDIDMD5", + DIDSHA1: "anyDIDSHA1", + DPIDMD5: "anyDPIDMD5", + DPIDSHA1: "anyDPIDSHA1", + MACSHA1: "anyMACSHA1", + MACMD5: "anyMACMD5", + IFA: "anyIFA", + IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", - Geo: &openrtb.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, + Geo: device.Geo, }, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4None, ipv6: ScrubStrategyIPV6Lowest16, - geo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "IPv6 None & Geo Reduced", - expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoReducedPrecision, + geo: ScrubStrategyGeoNone, }, { - description: "IPv6 Lowest 32 & Geo None", + description: "Isolated - IPv6 - Lowest 32", expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", + DIDMD5: "anyDIDMD5", + DIDSHA1: "anyDIDSHA1", + DPIDMD5: "anyDPIDMD5", + DPIDSHA1: "anyDPIDSHA1", + MACSHA1: "anyMACSHA1", + MACMD5: "anyMACMD5", + IFA: "anyIFA", + IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, + Geo: device.Geo, }, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4None, ipv6: ScrubStrategyIPV6Lowest32, geo: ScrubStrategyGeoNone, }, { - description: "IPv6 Lowest 16 & Geo None", + description: "Isolated - Geo - Reduced Precision", expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", + DIDMD5: "anyDIDMD5", + DIDSHA1: "anyDIDSHA1", + DPIDMD5: "anyDPIDMD5", + DPIDSHA1: "anyDPIDSHA1", + MACSHA1: "anyMACSHA1", + MACMD5: "anyMACMD5", + IFA: "anyIFA", + IP: "1.2.3.4", + IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", Geo: &openrtb.Geo{ - Lat: 123.456, + Lat: 123.46, Lon: 678.89, Metro: "some metro", City: "some city", ZIP: "some zip", }, }, - ipv6: ScrubStrategyIPV6Lowest16, - geo: ScrubStrategyGeoNone, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4None, + ipv6: ScrubStrategyIPV6None, + geo: ScrubStrategyGeoReducedPrecision, }, { - description: "IPv6 None & Geo None", + description: "Isolated - Geo - Full", expected: &openrtb.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", + DIDMD5: "anyDIDMD5", + DIDSHA1: "anyDIDSHA1", + DPIDMD5: "anyDPIDMD5", + DPIDSHA1: "anyDPIDSHA1", + MACSHA1: "anyMACSHA1", + MACMD5: "anyMACMD5", + IFA: "anyIFA", + IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, + Geo: &openrtb.Geo{}, }, + id: ScrubStrategyDeviceIDNone, + ipv4: ScrubStrategyIPV4None, ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, + geo: ScrubStrategyGeoFull, }, } for _, test := range testCases { - result := NewScrubber().ScrubDevice(device, test.ipv6, test.geo) + result := NewScrubber().ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) assert.Equal(t, test.expected, result, test.description) } } func TestScrubDeviceNil(t *testing.T) { - result := NewScrubber().ScrubDevice(nil, ScrubStrategyIPV6None, ScrubStrategyGeoNone) + result := NewScrubber().ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) assert.Nil(t, result) } @@ -458,7 +418,7 @@ func TestScrubIPV4(t *testing.T) { } for _, test := range testCases { - result := scrubIPV4(test.IP) + result := scrubIPV4Lowest8(test.IP) assert.Equal(t, test.cleanedIP, result, test.description) } } From d3ba8a94e3830af798b26a5c97c10ff0d94b44de Mon Sep 17 00:00:00 2001 From: Bill Newman Date: Tue, 15 Sep 2020 17:21:59 +0300 Subject: [PATCH 199/603] New colossus adapter [Clean branch] (#1495) Co-authored-by: Aiholkin --- adapters/colossus/colossus.go | 137 ++++++++++++++++++ adapters/colossus/colossus_test.go | 12 ++ .../colossustest/exemplary/simple-banner.json | 132 +++++++++++++++++ .../colossustest/exemplary/simple-video.json | 119 +++++++++++++++ .../exemplary/simple-web-banner.json | 133 +++++++++++++++++ .../colossus/colossustest/params/banner.json | 3 + .../colossustest/params/race/banner.json | 3 + .../colossustest/params/race/video.json | 3 + .../colossus/colossustest/params/video.json | 3 + .../supplemental/bad-imp-ext.json | 42 ++++++ .../supplemental/bad_response.json | 85 +++++++++++ .../supplemental/bad_status_code.json | 79 ++++++++++ .../supplemental/empty_imp_ext.json | 38 +++++ .../supplemental/imp_ext_empty_object.json | 39 +++++ .../supplemental/imp_ext_string.json | 39 +++++ .../colossustest/supplemental/status-204.json | 79 ++++++++++ .../colossustest/supplemental/status-404.json | 85 +++++++++++ .../supplemental/string_imp_ext.json | 39 +++++ adapters/colossus/params_test.go | 46 ++++++ adapters/colossus/usersync.go | 13 ++ adapters/colossus/usersync_test.go | 35 +++++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_colossus.go | 6 + static/bidder-info/colossus.yaml | 11 ++ static/bidder-params/colossus.json | 14 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 29 files changed, 1204 insertions(+) create mode 100644 adapters/colossus/colossus.go create mode 100644 adapters/colossus/colossus_test.go create mode 100644 adapters/colossus/colossustest/exemplary/simple-banner.json create mode 100644 adapters/colossus/colossustest/exemplary/simple-video.json create mode 100644 adapters/colossus/colossustest/exemplary/simple-web-banner.json create mode 100644 adapters/colossus/colossustest/params/banner.json create mode 100644 adapters/colossus/colossustest/params/race/banner.json create mode 100644 adapters/colossus/colossustest/params/race/video.json create mode 100644 adapters/colossus/colossustest/params/video.json create mode 100644 adapters/colossus/colossustest/supplemental/bad-imp-ext.json create mode 100644 adapters/colossus/colossustest/supplemental/bad_response.json create mode 100644 adapters/colossus/colossustest/supplemental/bad_status_code.json create mode 100644 adapters/colossus/colossustest/supplemental/empty_imp_ext.json create mode 100644 adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json create mode 100644 adapters/colossus/colossustest/supplemental/imp_ext_string.json create mode 100644 adapters/colossus/colossustest/supplemental/status-204.json create mode 100644 adapters/colossus/colossustest/supplemental/status-404.json create mode 100644 adapters/colossus/colossustest/supplemental/string_imp_ext.json create mode 100644 adapters/colossus/params_test.go create mode 100644 adapters/colossus/usersync.go create mode 100644 adapters/colossus/usersync_test.go create mode 100644 openrtb_ext/imp_colossus.go create mode 100644 static/bidder-info/colossus.yaml create mode 100644 static/bidder-params/colossus.json diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go new file mode 100644 index 00000000000..89cd49d2881 --- /dev/null +++ b/adapters/colossus/colossus.go @@ -0,0 +1,137 @@ +package colossus + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type ColossusAdapter struct { + URI string +} + +// NewColossusBidder Initializes the Bidder +func NewColossusBidder(endpoint string) *ColossusAdapter { + return &ColossusAdapter{ + URI: endpoint, + } +} + +// MakeRequests create bid request for colossus demand +func (a *ColossusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var err error + var tagID string + + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb.Imp{imp} + + tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") + if err != nil { + errs = append(errs, err) + continue + } + + reqCopy.Imp[0].TagID = tagID + + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + } + return adapterRequests, errs +} + +func (a *ColossusAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { + + var errs []error + + reqJSON, err := json.Marshal(request) + + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }, errs +} + +// MakeBids makes the bids +func (a *ColossusAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusNotFound { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType, nil + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + } +} diff --git a/adapters/colossus/colossus_test.go b/adapters/colossus/colossus_test.go new file mode 100644 index 00000000000..f4fd12f3fab --- /dev/null +++ b/adapters/colossus/colossus_test.go @@ -0,0 +1,12 @@ +package colossus + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + colossusAdapter := NewColossusBidder("http://example.com/?c=o&m=rtb") + adapterstest.RunJSONBidderTest(t, "colossustest", colossusAdapter) +} diff --git a/adapters/colossus/colossustest/exemplary/simple-banner.json b/adapters/colossus/colossustest/exemplary/simple-banner.json new file mode 100644 index 00000000000..1adc7010ed8 --- /dev/null +++ b/adapters/colossus/colossustest/exemplary/simple-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": { + "bidder": { + "TagID": "61317" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": { + "bidder": { + "TagID": "61317" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/colossus/colossustest/exemplary/simple-video.json b/adapters/colossus/colossustest/exemplary/simple-video.json new file mode 100644 index 00000000000..78516fcef31 --- /dev/null +++ b/adapters/colossus/colossustest/exemplary/simple-video.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "61318" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "tagid": "61318", + "ext": { + "bidder": { + "TagID": "61318" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "colossus" + } + ], + "cur": "USD" + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/colossus/colossustest/exemplary/simple-web-banner.json b/adapters/colossus/colossustest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..37baf3d97dd --- /dev/null +++ b/adapters/colossus/colossustest/exemplary/simple-web-banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "bidder": { + "TagID": "1" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "colossus" + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/colossus/colossustest/params/banner.json b/adapters/colossus/colossustest/params/banner.json new file mode 100644 index 00000000000..7c2643d4901 --- /dev/null +++ b/adapters/colossus/colossustest/params/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "61317" +} diff --git a/adapters/colossus/colossustest/params/race/banner.json b/adapters/colossus/colossustest/params/race/banner.json new file mode 100644 index 00000000000..7c2643d4901 --- /dev/null +++ b/adapters/colossus/colossustest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "61317" +} diff --git a/adapters/colossus/colossustest/params/race/video.json b/adapters/colossus/colossustest/params/race/video.json new file mode 100644 index 00000000000..56f865c71d9 --- /dev/null +++ b/adapters/colossus/colossustest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "61318" +} diff --git a/adapters/colossus/colossustest/params/video.json b/adapters/colossus/colossustest/params/video.json new file mode 100644 index 00000000000..56f865c71d9 --- /dev/null +++ b/adapters/colossus/colossustest/params/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "61318" +} diff --git a/adapters/colossus/colossustest/supplemental/bad-imp-ext.json b/adapters/colossus/colossustest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..13656337e5e --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/bad-imp-ext.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": { + "colossus": { + "TagID": "61317" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, +"expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } +] +} diff --git a/adapters/colossus/colossustest/supplemental/bad_response.json b/adapters/colossus/colossustest/supplemental/bad_response.json new file mode 100644 index 00000000000..c69b00c8e6e --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/bad_response.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/colossus/colossustest/supplemental/bad_status_code.json b/adapters/colossus/colossustest/supplemental/bad_status_code.json new file mode 100644 index 00000000000..f5b6a5748af --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/bad_status_code.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/colossus/colossustest/supplemental/empty_imp_ext.json b/adapters/colossus/colossustest/supplemental/empty_imp_ext.json new file mode 100644 index 00000000000..00e1cf60fb7 --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/empty_imp_ext.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json b/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json new file mode 100644 index 00000000000..e9c1f257aba --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/colossus/colossustest/supplemental/imp_ext_string.json b/adapters/colossus/colossustest/supplemental/imp_ext_string.json new file mode 100644 index 00000000000..362a8fa4df8 --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/imp_ext_string.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/colossus/colossustest/supplemental/status-204.json b/adapters/colossus/colossustest/supplemental/status-204.json new file mode 100644 index 00000000000..73f8bc71f23 --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "17", + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }] +} diff --git a/adapters/colossus/colossustest/supplemental/status-404.json b/adapters/colossus/colossustest/supplemental/status-404.json new file mode 100644 index 00000000000..676eb8bb2f4 --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/status-404.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://example.com/?c=o&m=rtb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/colossus/colossustest/supplemental/string_imp_ext.json b/adapters/colossus/colossustest/supplemental/string_imp_ext.json new file mode 100644 index 00000000000..362a8fa4df8 --- /dev/null +++ b/adapters/colossus/colossustest/supplemental/string_imp_ext.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "61317", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Key path not found", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/colossus/params_test.go b/adapters/colossus/params_test.go new file mode 100644 index 00000000000..2883de2f53e --- /dev/null +++ b/adapters/colossus/params_test.go @@ -0,0 +1,46 @@ +package colossus + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// TestValidParams makes sure that the colossus schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderColossus, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected colossus params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the colossus schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderColossus, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"TagID": "61317"}`, +} + +var invalidParams = []string{ + `{"id": "123"}`, + `{"tagid": "123"}`, + `{"TagID": 16}`, +} diff --git a/adapters/colossus/usersync.go b/adapters/colossus/usersync.go new file mode 100644 index 00000000000..a4e82ee3bde --- /dev/null +++ b/adapters/colossus/usersync.go @@ -0,0 +1,13 @@ +package colossus + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +// NewColossusSyncer returns colossus syncer +func NewColossusSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("colossus", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/colossus/usersync_test.go b/adapters/colossus/usersync_test.go new file mode 100644 index 00000000000..79d5483d528 --- /dev/null +++ b/adapters/colossus/usersync_test.go @@ -0,0 +1,35 @@ +package colossus + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestColossusSyncer(t *testing.T) { + syncURL := "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewColossusSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "A", + }, + CCPA: ccpa.Policy{ + Value: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://sync.colossusssp.com/pbs.gif?gdpr=0&gdpr_consent=A&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 59ba55ebe26..5731b65d567 100755 --- a/config/config.go +++ b/config/config.go @@ -693,6 +693,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderColossus, "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcolossus%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -911,6 +912,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") + v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index a160e87aad7..5bb788b63b9 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -31,6 +31,7 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" @@ -118,6 +119,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo), openrtb_ext.BidderBeintoo: beintoo.NewBeintooBidder(cfg.Adapters[string(openrtb_ext.BidderBeintoo)].Endpoint), openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBrightroll)].ExtraAdapterInfo), + openrtb_ext.BidderColossus: colossus.NewColossusBidder(cfg.Adapters[string(openrtb_ext.BidderColossus)].Endpoint), openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 876eeab86bd..221f97c9697 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -49,6 +49,7 @@ const ( BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBrightroll BidderName = "brightroll" + BidderColossus BidderName = "colossus" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" BidderCpmstar BidderName = "cpmstar" @@ -133,6 +134,7 @@ var BidderMap = map[string]BidderName{ "beachfront": BidderBeachfront, "beintoo": BidderBeintoo, "brightroll": BidderBrightroll, + "colossus": BidderColossus, "consumable": BidderConsumable, "conversant": BidderConversant, "cpmstar": BidderCpmstar, diff --git a/openrtb_ext/imp_colossus.go b/openrtb_ext/imp_colossus.go new file mode 100644 index 00000000000..8969000558d --- /dev/null +++ b/openrtb_ext/imp_colossus.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpColossus defines colossus specifiec param +type ExtImpColossus struct { + TagID string `json:"TagID"` +} diff --git a/static/bidder-info/colossus.yaml b/static/bidder-info/colossus.yaml new file mode 100644 index 00000000000..901c824c603 --- /dev/null +++ b/static/bidder-info/colossus.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "support@huddledmasses.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/colossus.json b/static/bidder-params/colossus.json new file mode 100644 index 00000000000..f2732fa0854 --- /dev/null +++ b/static/bidder-params/colossus.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Colossus Adapter Params", + "description": "A schema which validates params accepted by the Colossus adapter", + + "type": "object", + "properties": { + "TagID": { + "type": "string", + "description": "An ID which identifies the colossus ad tag" + } + }, + "required" : [ "TagID" ] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 89540ea205b..c6ae984efc9 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -23,6 +23,7 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" @@ -99,6 +100,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 9197ed9507d..2cf0b2513c5 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -31,6 +31,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBeachfront): syncConfig, string(openrtb_ext.BidderBeintoo): syncConfig, string(openrtb_ext.BidderBrightroll): syncConfig, + string(openrtb_ext.BidderColossus): syncConfig, string(openrtb_ext.BidderConsumable): syncConfig, string(openrtb_ext.BidderConversant): syncConfig, string(openrtb_ext.BidderCpmstar): syncConfig, From 7b59a4bd49e502b05f81624bcc908259aec757cf Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Tue, 15 Sep 2020 09:12:57 -0700 Subject: [PATCH 200/603] New: InMobi Prebid Server Adapter (#1489) * Adding InMobi adapter * code review feedback, also explicitly working with Imp[0], as we don't support multiple impressions * less tolerant bidder params due to sneaky 1.13 -> 1.14+ change --- adapters/inmobi/inmobi.go | 127 ++++++++++++++++++ adapters/inmobi/inmobi_test.go | 10 ++ .../inmobitest/exemplary/simple-banner.json | 107 +++++++++++++++ .../inmobitest/exemplary/simple-video.json | 109 +++++++++++++++ .../inmobi/inmobitest/params/race/banner.json | 3 + .../inmobi/inmobitest/params/race/video.json | 3 + .../inmobi/inmobitest/supplemental/204.json | 61 +++++++++ .../inmobi/inmobitest/supplemental/400.json | 67 +++++++++ .../supplemental/banner-format-coersion.json | 113 ++++++++++++++++ .../supplemental/ext-unmarshal-err.json | 28 ++++ .../supplemental/missing-plc-error.json | 28 ++++ .../inmobitest/supplemental/no-imp-error.json | 13 ++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_inmobi.go | 5 + static/bidder-info/inmobi.yaml | 8 ++ static/bidder-params/inmobi.json | 13 ++ usersync/usersyncers/syncer_test.go | 1 + 19 files changed, 701 insertions(+) create mode 100644 adapters/inmobi/inmobi.go create mode 100644 adapters/inmobi/inmobi_test.go create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-banner.json create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-video.json create mode 100644 adapters/inmobi/inmobitest/params/race/banner.json create mode 100644 adapters/inmobi/inmobitest/params/race/video.json create mode 100644 adapters/inmobi/inmobitest/supplemental/204.json create mode 100644 adapters/inmobi/inmobitest/supplemental/400.json create mode 100644 adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json create mode 100644 adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json create mode 100644 adapters/inmobi/inmobitest/supplemental/missing-plc-error.json create mode 100644 adapters/inmobi/inmobitest/supplemental/no-imp-error.json create mode 100644 openrtb_ext/imp_inmobi.go create mode 100644 static/bidder-info/inmobi.yaml create mode 100644 static/bidder-params/inmobi.json diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go new file mode 100644 index 00000000000..4d46ffb8f1e --- /dev/null +++ b/adapters/inmobi/inmobi.go @@ -0,0 +1,127 @@ +package inmobi + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type InMobiAdapter struct { + endPoint string +} + +func NewInMobiAdapter(endpoint string) *InMobiAdapter { + return &InMobiAdapter{ + endPoint: endpoint, + } +} + +func (a *InMobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the request", + }} + } + + if err := preprocess(&request.Imp[0]); err != nil { + errs = append(errs, err) + return nil, errs + } + + reqJson, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endPoint, + Body: reqJson, + Headers: headers, + }}, errs +} + +func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected http status code: %d", response.StatusCode), + }} + } + + var serverBidResponse openrtb.BidResponse + if err := json.Unmarshal(response.Body, &serverBidResponse); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range serverBidResponse.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, nil +} + +func preprocess(imp *openrtb.Imp) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + var inMobiExt openrtb_ext.ExtImpInMobi + if err := json.Unmarshal(bidderExt.Bidder, &inMobiExt); err != nil { + return &errortypes.BadInput{Message: "bad InMobi bidder ext"} + } + + if len(inMobiExt.Plc) == 0 { + return &errortypes.BadInput{Message: "'plc' is a required attribute for InMobi's bidder ext"} + } + + if imp.Banner != nil { + banner := *imp.Banner + imp.Banner = &banner + if (banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0) && len(banner.Format) > 0 { + format := banner.Format[0] + banner.W = &format.W + banner.H = &format.H + } + } + + return nil +} + +func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + break + } + } + return mediaType +} diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go new file mode 100644 index 00000000000..6aa58d97222 --- /dev/null +++ b/adapters/inmobi/inmobi_test.go @@ -0,0 +1,10 @@ +package inmobi + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "inmobitest", NewInMobiAdapter("https://api.w.inmobi.com/showad/openrtb/bidder/prebid")) +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..4345ef8ff66 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-banner.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-video.json new file mode 100644 index 00000000000..20b3c0cc810 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-video.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1598991608990" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1598991608990" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": " ", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": " ", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "video" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/params/race/banner.json b/adapters/inmobi/inmobitest/params/race/banner.json new file mode 100644 index 00000000000..7791393fc99 --- /dev/null +++ b/adapters/inmobi/inmobitest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "plc": "1596825400965" +} diff --git a/adapters/inmobi/inmobitest/params/race/video.json b/adapters/inmobi/inmobitest/params/race/video.json new file mode 100644 index 00000000000..74a44b6e6f9 --- /dev/null +++ b/adapters/inmobi/inmobitest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "plc": "1598991608990" +} diff --git a/adapters/inmobi/inmobitest/supplemental/204.json b/adapters/inmobi/inmobitest/supplemental/204.json new file mode 100644 index 00000000000..c811763678c --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/204.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }] +} diff --git a/adapters/inmobi/inmobitest/supplemental/400.json b/adapters/inmobi/inmobitest/supplemental/400.json new file mode 100644 index 00000000000..2df5c85aaca --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 400", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json new file mode 100644 index 00000000000..514f86817c9 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "device-ifa", + "ip": "1.1.1.1", + "ua": "device-ua" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "format": [{ + "w": 320, + "h": 50 + }] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "device-ifa", + "ip": "1.1.1.1", + "ua": "device-ua" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "format": [{ + "w": 320, + "h": 50 + }], + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json new file mode 100644 index 00000000000..957a2f6f952 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + { + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "plc": true + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "bad InMobi bidder ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json new file mode 100644 index 00000000000..52697cf90c6 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + { + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "a": 1 + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "'plc' is a required attribute for InMobi's bidder ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/no-imp-error.json b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json new file mode 100644 index 00000000000..6c6c363425a --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the request", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 5731b65d567..53daf117fdf 100755 --- a/config/config.go +++ b/config/config.go @@ -926,6 +926,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") + v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 5bb788b63b9..d428168921a 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -45,6 +45,7 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/kubient" @@ -135,6 +136,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), + openrtb_ext.BidderInMobi: inmobi.NewInMobiAdapter(cfg.Adapters[string(openrtb_ext.BidderInMobi)].Endpoint), openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 221f97c9697..dcfd663ebc7 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -64,6 +64,7 @@ const ( BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" BidderImprovedigital BidderName = "improvedigital" + BidderInMobi BidderName = "inmobi" BidderIx BidderName = "ix" BidderKidoz BidderName = "kidoz" BidderKubient BidderName = "kubient" @@ -149,6 +150,7 @@ var BidderMap = map[string]BidderName{ "grid": BidderGrid, "gumgum": BidderGumGum, "improvedigital": BidderImprovedigital, + "inmobi": BidderInMobi, "ix": BidderIx, "kidoz": BidderKidoz, "kubient": BidderKubient, diff --git a/openrtb_ext/imp_inmobi.go b/openrtb_ext/imp_inmobi.go new file mode 100644 index 00000000000..d74e3cac8b0 --- /dev/null +++ b/openrtb_ext/imp_inmobi.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpInMobi struct { + Plc string `json:"plc"` +} diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml new file mode 100644 index 00000000000..3f8cdd8cb91 --- /dev/null +++ b/static/bidder-info/inmobi.yaml @@ -0,0 +1,8 @@ +maintainer: + email: "prebid-support@inmobi.com" + +capabilities: + app: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/inmobi.json b/static/bidder-params/inmobi.json new file mode 100644 index 00000000000..631b3137b72 --- /dev/null +++ b/static/bidder-params/inmobi.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "InMobi Adapter Params", + "description": "A schema which validates params accepted by the InMobi adapter", + "type": "object", + "properties": { + "plc": { + "type": ["string"], + "description": "An ID corresponding to the placement selling the impression" + } + }, + "required": ["plc"] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 2cf0b2513c5..bd250489fdd 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -89,6 +89,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdhese: true, openrtb_ext.BidderAdoppler: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderInMobi: true, openrtb_ext.BidderKidoz: true, openrtb_ext.BidderKubient: true, openrtb_ext.BidderMobileFuse: true, From ab653bc8a3ea0e7bf814a1741a0c7eab2b1e5139 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 16 Sep 2020 18:04:48 -0400 Subject: [PATCH 201/603] Revert "Added new size 640x360 (Id: 198) (#1490)" (#1501) This reverts commit fa23f5c226df99a9a4ef318100fdb7d84d3e40fa. --- adapters/rubicon/rubicon.go | 1 - 1 file changed, 1 deletion(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 7d6e0e12039..56ae7b2f792 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -236,7 +236,6 @@ var rubiSizeMap = map[rubiSize]int{ {w: 800, h: 250}: 125, {w: 200, h: 600}: 126, {w: 640, h: 320}: 156, - {w: 640, h: 360}: 198, } // defines the contract for bidrequest.user.ext.eids[i].ext From f6624b7acc924f6e66b014825bf07d2edb072eb9 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 17 Sep 2020 01:48:17 -0400 Subject: [PATCH 202/603] CCPA Publisher No Sale Relationships (#1465) --- adapters/33across/usersync_test.go | 2 +- adapters/adkernel/usersync_test.go | 2 +- adapters/adkernelAdn/usersync_test.go | 2 +- adapters/adman/usersync_test.go | 2 +- adapters/admixer/usersync_test.go | 7 +- adapters/adtarget/usersync_test.go | 5 +- adapters/aja/usersync_test.go | 5 +- adapters/avocet/usersync_test.go | 2 +- adapters/beachfront/usersync_test.go | 2 +- adapters/beintoo/usersync_test.go | 2 +- adapters/consumable/consumable.go | 13 +- adapters/consumable/usersync_test.go | 2 +- adapters/datablocks/usersync_test.go | 2 +- adapters/emx_digital/usersync_test.go | 2 +- adapters/engagebdr/usersync_test.go | 2 +- adapters/gamoshi/usersync_test.go | 2 +- adapters/gumgum/usersync_test.go | 2 +- adapters/improvedigital/usersync_test.go | 2 +- adapters/marsmedia/usersync_test.go | 2 +- adapters/nanointeractive/usersync_test.go | 24 +- adapters/pubmatic/usersync_test.go | 2 +- adapters/rhythmone/usersync_test.go | 2 +- adapters/sharethrough/butler.go | 15 +- adapters/smartadserver/usersync_test.go | 2 +- adapters/syncer.go | 2 +- adapters/syncer_test.go | 2 +- adapters/unruly/usersync_test.go | 2 +- adapters/valueimpression/usersync_test.go | 2 +- adapters/visx/usersync_test.go | 2 +- adapters/zeroclickfraud/usersync_test.go | 2 +- endpoints/auction.go | 6 +- endpoints/auction_test.go | 5 +- endpoints/cookie_sync.go | 50 +- endpoints/openrtb2/amp_auction.go | 41 +- endpoints/openrtb2/auction.go | 35 +- endpoints/openrtb2/auction_test.go | 48 ++ .../exchangetest/ccpa-nosale-any-bidder.json | 75 +++ .../ccpa-nosale-specific-bidder.json | 75 +++ exchange/utils.go | 70 +- exchange/utils_test.go | 80 ++- openrtb_ext/request.go | 5 + privacy/ccpa/consentwriter.go | 25 + privacy/ccpa/consentwriter_test.go | 51 ++ privacy/ccpa/parsedpolicy.go | 137 ++++ privacy/ccpa/parsedpolicy_test.go | 391 +++++++++++ privacy/ccpa/policy.go | 213 +++--- privacy/ccpa/policy_test.go | 630 +++++++++++------- privacy/enforcer.go | 43 ++ privacy/enforcer_test.go | 18 + privacy/gdpr/consentwriter.go | 44 ++ privacy/gdpr/consentwriter_test.go | 101 +++ privacy/gdpr/policy.go | 40 +- privacy/gdpr/policy_test.go | 113 +--- privacy/lmt/policy.go | 14 +- privacy/lmt/policy_test.go | 68 +- privacy/policies.go | 52 +- privacy/policies_test.go | 119 ---- privacy/writer.go | 18 + privacy/writer_test.go | 25 + 59 files changed, 1962 insertions(+), 747 deletions(-) create mode 100644 exchange/exchangetest/ccpa-nosale-any-bidder.json create mode 100644 exchange/exchangetest/ccpa-nosale-specific-bidder.json create mode 100644 privacy/ccpa/consentwriter.go create mode 100644 privacy/ccpa/consentwriter_test.go create mode 100644 privacy/ccpa/parsedpolicy.go create mode 100644 privacy/ccpa/parsedpolicy_test.go create mode 100644 privacy/enforcer.go create mode 100644 privacy/enforcer_test.go create mode 100644 privacy/gdpr/consentwriter.go create mode 100644 privacy/gdpr/consentwriter_test.go delete mode 100644 privacy/policies_test.go create mode 100644 privacy/writer.go create mode 100644 privacy/writer_test.go diff --git a/adapters/33across/usersync_test.go b/adapters/33across/usersync_test.go index a5e301b1082..a9eb4e57908 100644 --- a/adapters/33across/usersync_test.go +++ b/adapters/33across/usersync_test.go @@ -23,7 +23,7 @@ func Test33AcrossSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/adkernel/usersync_test.go b/adapters/adkernel/usersync_test.go index 0d539d11ee0..aeacf00b7f0 100644 --- a/adapters/adkernel/usersync_test.go +++ b/adapters/adkernel/usersync_test.go @@ -23,7 +23,7 @@ func TestAdkernelAdnSyncer(t *testing.T) { Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/adkernelAdn/usersync_test.go b/adapters/adkernelAdn/usersync_test.go index ecc759bdf70..92d688e6117 100644 --- a/adapters/adkernelAdn/usersync_test.go +++ b/adapters/adkernelAdn/usersync_test.go @@ -23,7 +23,7 @@ func TestAdkernelAdnSyncer(t *testing.T) { Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/adman/usersync_test.go b/adapters/adman/usersync_test.go index 55a6e2cec97..25da77db7ed 100644 --- a/adapters/adman/usersync_test.go +++ b/adapters/adman/usersync_test.go @@ -23,7 +23,7 @@ func TestAdmanSyncer(t *testing.T) { Consent: "ANDFJDS", }, CCPA: ccpa.Policy{ - Value: "1-YY", + Consent: "1-YY", }, }) diff --git a/adapters/admixer/usersync_test.go b/adapters/admixer/usersync_test.go index a5715c64a46..d31f7b10fb1 100644 --- a/adapters/admixer/usersync_test.go +++ b/adapters/admixer/usersync_test.go @@ -1,12 +1,13 @@ package admixer import ( + "testing" + "text/template" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" - "testing" - "text/template" ) func TestAdmixerSyncer(t *testing.T) { @@ -22,7 +23,7 @@ func TestAdmixerSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go index 3ab2ed5b5df..ddba9e7a720 100644 --- a/adapters/adtarget/usersync_test.go +++ b/adapters/adtarget/usersync_test.go @@ -2,10 +2,11 @@ package adtarget import ( "fmt" - "github.com/prebid/prebid-server/privacy/ccpa" "testing" "text/template" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" @@ -25,7 +26,7 @@ func TestAdtargetSyncer(t *testing.T) { Consent: "123", }, CCPA: ccpa.Policy{ - Value: "1-YY", + Consent: "1-YY", }, }) diff --git a/adapters/aja/usersync_test.go b/adapters/aja/usersync_test.go index dbb66cc9ae2..4b6c90ef141 100644 --- a/adapters/aja/usersync_test.go +++ b/adapters/aja/usersync_test.go @@ -1,10 +1,11 @@ package aja import ( - "github.com/prebid/prebid-server/privacy/ccpa" "testing" "text/template" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" @@ -23,7 +24,7 @@ func TestAJASyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go index 8fba403f1b1..3df39b77fce 100644 --- a/adapters/avocet/usersync_test.go +++ b/adapters/avocet/usersync_test.go @@ -23,7 +23,7 @@ func TestAvocetSyncer(t *testing.T) { Consent: "ConsentString", }, CCPA: ccpa.Policy{ - Value: "PrivacyString", + Consent: "PrivacyString", }, }) diff --git a/adapters/beachfront/usersync_test.go b/adapters/beachfront/usersync_test.go index 0267ac05eb7..db4d825eb5a 100644 --- a/adapters/beachfront/usersync_test.go +++ b/adapters/beachfront/usersync_test.go @@ -23,7 +23,7 @@ func TestBeachfrontSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/beintoo/usersync_test.go b/adapters/beintoo/usersync_test.go index 2cfca010226..880d6a84cee 100644 --- a/adapters/beintoo/usersync_test.go +++ b/adapters/beintoo/usersync_test.go @@ -23,7 +23,7 @@ func TestBeintooSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index ff7451f15f7..18ece8d4c4a 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -3,15 +3,16 @@ package consumable import ( "encoding/json" "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy/ccpa" - "net/http" - "net/url" - "strconv" - "strings" ) type ConsumableAdapter struct { @@ -136,9 +137,9 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a gdpr := bidGdpr{} - ccpaPolicy, err := ccpa.ReadPolicy(request) + ccpaPolicy, err := ccpa.ReadFromRequest(request) if err == nil { - body.CCPA = ccpaPolicy.Value + body.CCPA = ccpaPolicy.Consent } // TODO: Replace with gdpr.ReadPolicy when it is available diff --git a/adapters/consumable/usersync_test.go b/adapters/consumable/usersync_test.go index 017cb72975b..ef71c0b18c7 100644 --- a/adapters/consumable/usersync_test.go +++ b/adapters/consumable/usersync_test.go @@ -23,7 +23,7 @@ func TestConsumableSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/datablocks/usersync_test.go b/adapters/datablocks/usersync_test.go index f8500ab9b03..a7518e9b226 100644 --- a/adapters/datablocks/usersync_test.go +++ b/adapters/datablocks/usersync_test.go @@ -23,7 +23,7 @@ func TestDatablocksSyncer(t *testing.T) { Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/emx_digital/usersync_test.go b/adapters/emx_digital/usersync_test.go index 0e76936cea4..59d66d87808 100644 --- a/adapters/emx_digital/usersync_test.go +++ b/adapters/emx_digital/usersync_test.go @@ -23,7 +23,7 @@ func TestEMXDigitalSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/engagebdr/usersync_test.go b/adapters/engagebdr/usersync_test.go index 45e1e41e196..3a6c179addf 100644 --- a/adapters/engagebdr/usersync_test.go +++ b/adapters/engagebdr/usersync_test.go @@ -23,7 +23,7 @@ func TestEngageBDRSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/gamoshi/usersync_test.go b/adapters/gamoshi/usersync_test.go index b8e3e327e44..43dc88a4953 100644 --- a/adapters/gamoshi/usersync_test.go +++ b/adapters/gamoshi/usersync_test.go @@ -18,7 +18,7 @@ func TestGamoshiSyncer(t *testing.T) { syncer := NewGamoshiSyncer(syncURLTemplate) syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ CCPA: ccpa.Policy{ - Value: "anyValue", + Consent: "anyValue", }, }) diff --git a/adapters/gumgum/usersync_test.go b/adapters/gumgum/usersync_test.go index 3606f6ae04c..9c6dc420600 100644 --- a/adapters/gumgum/usersync_test.go +++ b/adapters/gumgum/usersync_test.go @@ -23,7 +23,7 @@ func TestGumGumSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/improvedigital/usersync_test.go b/adapters/improvedigital/usersync_test.go index c928ebf123d..35ea89cf894 100644 --- a/adapters/improvedigital/usersync_test.go +++ b/adapters/improvedigital/usersync_test.go @@ -23,7 +23,7 @@ func TestImprovedigitalSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/marsmedia/usersync_test.go b/adapters/marsmedia/usersync_test.go index 67276a35fb6..f019c014516 100644 --- a/adapters/marsmedia/usersync_test.go +++ b/adapters/marsmedia/usersync_test.go @@ -23,7 +23,7 @@ func TestMarsmediaSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/nanointeractive/usersync_test.go b/adapters/nanointeractive/usersync_test.go index ec9787bc20d..fa78664928f 100644 --- a/adapters/nanointeractive/usersync_test.go +++ b/adapters/nanointeractive/usersync_test.go @@ -1,11 +1,12 @@ package nanointeractive import ( - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" "testing" "text/template" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -17,16 +18,15 @@ func TestNewNanoInteractiveSyncer(t *testing.T) { ) userSync := NewNanoInteractiveSyncer(syncURLTemplate) - syncInfo, err := userSync.GetUsersyncInfo( - privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Value: "1NYN", - }, - }) + syncInfo, err := userSync.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + }, + CCPA: ccpa.Policy{ + Consent: "1NYN", + }, + }) assert.NoError(t, err) assert.Equal(t, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr=1&consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) diff --git a/adapters/pubmatic/usersync_test.go b/adapters/pubmatic/usersync_test.go index dd4a086c453..d6cd9f78af7 100644 --- a/adapters/pubmatic/usersync_test.go +++ b/adapters/pubmatic/usersync_test.go @@ -23,7 +23,7 @@ func TestPubmaticSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/rhythmone/usersync_test.go b/adapters/rhythmone/usersync_test.go index cee6e9b0259..85ecba2a8ab 100644 --- a/adapters/rhythmone/usersync_test.go +++ b/adapters/rhythmone/usersync_test.go @@ -23,7 +23,7 @@ func TestRhythmoneSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index 522bbc4967e..36af79c4534 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -3,16 +3,17 @@ package sharethrough import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy/ccpa" "net/http" "net/url" "regexp" "strconv" "time" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy/ccpa" ) const defaultTmax = 10000 // 10 sec @@ -97,8 +98,8 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openr } usPolicySignal := "" - if usPolicy, err := ccpa.ReadPolicy(request); err == nil { - usPolicySignal = usPolicy.Value + if usPolicy, err := ccpa.ReadFromRequest(request); err == nil { + usPolicySignal = usPolicy.Consent } return &adapters.RequestData{ diff --git a/adapters/smartadserver/usersync_test.go b/adapters/smartadserver/usersync_test.go index e279b49e017..c4e6660693f 100644 --- a/adapters/smartadserver/usersync_test.go +++ b/adapters/smartadserver/usersync_test.go @@ -23,7 +23,7 @@ func TestSmartadserverSyncer(t *testing.T) { Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA", }, CCPA: ccpa.Policy{ - Value: "1YNN", + Consent: "1YNN", }, }) diff --git a/adapters/syncer.go b/adapters/syncer.go index c212a4366c9..122bcc7ed38 100644 --- a/adapters/syncer.go +++ b/adapters/syncer.go @@ -46,7 +46,7 @@ func (s *Syncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.Us syncURL, err := macros.ResolveMacros(*s.urlTemplate, macros.UserSyncTemplateParams{ GDPR: privacyPolicies.GDPR.Signal, GDPRConsent: privacyPolicies.GDPR.Consent, - USPrivacy: privacyPolicies.CCPA.Value, + USPrivacy: privacyPolicies.CCPA.Consent, }) if err != nil { return nil, err diff --git a/adapters/syncer_test.go b/adapters/syncer_test.go index 9be523091dd..ca33a9a130d 100644 --- a/adapters/syncer_test.go +++ b/adapters/syncer_test.go @@ -17,7 +17,7 @@ func TestGetUsersyncInfo(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, } diff --git a/adapters/unruly/usersync_test.go b/adapters/unruly/usersync_test.go index bdab254f370..2f0e07d813a 100644 --- a/adapters/unruly/usersync_test.go +++ b/adapters/unruly/usersync_test.go @@ -23,7 +23,7 @@ func TestUnrulySyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go index 63f123055a9..ffb3f372bd7 100644 --- a/adapters/valueimpression/usersync_test.go +++ b/adapters/valueimpression/usersync_test.go @@ -23,7 +23,7 @@ func TestValueImpressionSyncer(t *testing.T) { Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/adapters/visx/usersync_test.go b/adapters/visx/usersync_test.go index a77136c9240..b410cda6061 100644 --- a/adapters/visx/usersync_test.go +++ b/adapters/visx/usersync_test.go @@ -23,7 +23,7 @@ func TestVisxSyncer(t *testing.T) { Consent: "B", }, CCPA: ccpa.Policy{ - Value: "C", + Consent: "C", }, }) diff --git a/adapters/zeroclickfraud/usersync_test.go b/adapters/zeroclickfraud/usersync_test.go index 30ade771a4c..5e8f8fdf111 100644 --- a/adapters/zeroclickfraud/usersync_test.go +++ b/adapters/zeroclickfraud/usersync_test.go @@ -23,7 +23,7 @@ func TestZeroClickFraudSyncer(t *testing.T) { Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", }, CCPA: ccpa.Policy{ - Value: "1NYN", + Consent: "1NYN", }, }) diff --git a/endpoints/auction.go b/endpoints/auction.go index bf592e43b02..c6fd57123c7 100644 --- a/endpoints/auction.go +++ b/endpoints/auction.go @@ -24,7 +24,7 @@ import ( "github.com/prebid/prebid-server/pbsmetrics" pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/privacy" - gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/usersync" ) @@ -190,7 +190,7 @@ func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, pbsmetrics.AdapterLab } } -func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPolicy.Policy) bool { +func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { switch gdprPrivacyPolicy.Signal { case "0": return true @@ -511,7 +511,7 @@ func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bl if uid == "" { bidder.NoCookie = true privacyPolicies := privacy.Policies{ - GDPR: gdprPolicy.Policy{ + GDPR: gdprPrivacy.Policy{ Signal: req.ParseGDPR(), Consent: req.ParseConsent(), }, diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 028f119640a..1e41b02aaa2 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -387,11 +387,12 @@ func TestShouldUsersync(t *testing.T) { }, metricsEngine: nil, } - privacyPolicy := gdprPolicy.Policy{ + gdprPrivacyPolicy := gdprPolicy.Policy{ Signal: gdprApplies, Consent: consent, } - allowSyncs := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, privacyPolicy) + + allowSyncs := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) if allowSyncs != expectAllow { t.Errorf("Expected syncs: %t, allowed syncs: %t", expectAllow, allowSyncs) } diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 9787a8f78f2..60da4f0bd16 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -20,7 +20,7 @@ import ( "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/privacy/ccpa" - gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/usersync" ) @@ -105,24 +105,30 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h } } + parsedReq.filterExistingSyncs(deps.syncers, userSyncCookie, needSyncupForSameSite) + + adapterSyncs := make(map[openrtb_ext.BidderName]bool) + // assume all bidders will be privacy blocked + for _, b := range parsedReq.Bidders { + adapterSyncs[openrtb_ext.BidderName(b)] = true + } + privacyPolicy := privacy.Policies{ - GDPR: gdprPolicy.Policy{ + GDPR: gdprPrivacy.Policy{ Signal: gdprToString(parsedReq.GDPR), Consent: parsedReq.Consent, }, CCPA: ccpa.Policy{ - Value: parsedReq.USPrivacy, + Consent: parsedReq.USPrivacy, }, } - parsedReq.filterExistingSyncs(deps.syncers, userSyncCookie, needSyncupForSameSite) + parsedReq.filterForGDPR(deps.syncPermissions) - adapterSyncs := make(map[openrtb_ext.BidderName]bool) - // assume all bidders will be privacy blocked - for _, b := range parsedReq.Bidders { - adapterSyncs[openrtb_ext.BidderName(b)] = true + if deps.enforceCCPA { + parsedReq.filterForCCPA() } - parsedReq.filterForPrivacy(deps.syncPermissions, privacyPolicy, deps.enforceCCPA) + // surviving bidders are not privacy blocked for _, b := range parsedReq.Bidders { adapterSyncs[openrtb_ext.BidderName(b)] = false @@ -223,12 +229,7 @@ func (req *cookieSyncRequest) filterExistingSyncs(valid map[openrtb_ext.BidderNa } } -func (req *cookieSyncRequest) filterForPrivacy(permissions gdpr.Permissions, privacyPolicies privacy.Policies, enforceCCPA bool) { - if enforceCCPA && privacyPolicies.CCPA.ShouldEnforce() { - req.Bidders = nil - return - } - +func (req *cookieSyncRequest) filterForGDPR(permissions gdpr.Permissions) { if req.GDPR != nil && *req.GDPR == 0 { return } @@ -246,6 +247,25 @@ func (req *cookieSyncRequest) filterForPrivacy(permissions gdpr.Permissions, pri } } +func (req *cookieSyncRequest) filterForCCPA() { + validBidders := make(map[string]struct{}) + for _, v := range openrtb_ext.BidderMap { + validBidders[v.String()] = struct{}{} + } + + ccpaPolicy := &ccpa.Policy{Consent: req.USPrivacy} + ccpaParsedPolicy, err := ccpaPolicy.Parse(validBidders) + + if err == nil { + for i := 0; i < len(req.Bidders); i++ { + if ccpaParsedPolicy.ShouldEnforce(req.Bidders[i]) { + req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...) + i-- + } + } + } +} + // filterToLimit will enforce a max limit on cookiesyncs supplied, picking a random subset of syncs to get to the limit if over. func (req *cookieSyncRequest) filterToLimit() { if req.Limit <= 0 { diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 1e92569e260..d7442f5ecba 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -24,6 +24,8 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" @@ -403,17 +405,12 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope req.Imp[0].TagID = slot } - consent := readConsent(httpRequest.URL) - if consent != "" { - if policies, ok := privacy.ReadPoliciesFromConsent(consent); ok { - if err := policies.Write(req); err != nil { - return []error{err} - } - } else { - return []error{&errortypes.InvalidPrivacyConsent{ - Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), - }} - } + policyWriter, policyWriterErr := readPolicyFromUrl(httpRequest.URL) + if policyWriterErr != nil { + return []error{policyWriterErr} + } + if err := policyWriter.Write(req); err != nil { + return []error{err} } if timeout, err := strconv.ParseInt(httpRequest.FormValue("timeout"), 10, 64); err == nil { @@ -558,7 +555,27 @@ func setAmpExt(site *openrtb.Site, value string) { } } -func readConsent(url *url.URL) string { +func readPolicyFromUrl(url *url.URL) (privacy.PolicyWriter, error) { + consent := readConsentFromURL(url) + + if len(consent) == 0 { + return privacy.NilPolicyWriter{}, nil + } + + if gdpr.ValidateConsent(consent) { + return gdpr.ConsentWriter{consent}, nil + } + + if ccpa.ValidateConsent(consent) { + return ccpa.ConsentWriter{consent}, nil + } + + return privacy.NilPolicyWriter{}, &errortypes.InvalidPrivacyConsent{ + Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + } +} + +func readConsentFromURL(url *url.URL) string { if v := url.Query().Get("consent_string"); v != "" { return v } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d6cbc2285fb..b02b57861bd 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -318,39 +318,36 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { - errL = append(errL, errors.New("request.site or request.app must be defined, but not both.")) - return errL + return append(errL, errors.New("request.site or request.app must be defined, but not both.")) } if err := deps.validateSite(req.Site); err != nil { - errL = append(errL, err) - return errL + return append(errL, err) } if err := deps.validateApp(req.App); err != nil { - errL = append(errL, err) - return errL + return append(errL, err) } if err := validateUser(req.User, aliases); err != nil { - errL = append(errL, err) - return errL + return append(errL, err) } if err := validateRegs(req.Regs); err != nil { - errL = append(errL, err) - return errL + return append(errL, err) } - if policy, err := ccpa.ReadPolicy(req); err != nil { - errL = append(errL, errL...) - return errL - } else if err := policy.Validate(); err != nil { - errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) - - policy.Value = "" - if err := policy.Write(req); err != nil { - errL = append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) + if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { + return append(errL, err) + } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { + if _, invalidConsent := err.(*errortypes.InvalidPrivacyConsent); invalidConsent { + errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) + consentWriter := ccpa.ConsentWriter{""} + if err := consentWriter.Write(req); err != nil { + return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) + } + } else { + return append(errL, err) } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 925cffcebeb..58913bb58d6 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1339,6 +1339,54 @@ func TestCCPAInvalid(t *testing.T) { assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } +func TestNoSaleInvalid(t *testing.T) { + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{}, + pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + ui := uint64(1) + req := openrtb.BidRequest{ + ID: "someID", + Imp: []openrtb.Imp{ + { + ID: "imp-ID", + Banner: &openrtb.Banner{ + W: &ui, + H: &ui, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), + }, + }, + Site: &openrtb.Site{ + ID: "myID", + }, + Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"1NYN"}`), + }, + Ext: json.RawMessage(`{"prebid":{"nosale":["*", "appnexus"]}}`), + } + + errL := deps.validateRequest(&req) + + expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") + assert.ElementsMatch(t, errL, []error{expectedError}) +} + func TestValidateSourceTID(t *testing.T) { cfg := &config.Configuration{ AutoGenSourceTID: true, diff --git a/exchange/exchangetest/ccpa-nosale-any-bidder.json b/exchange/exchangetest/ccpa-nosale-any-bidder.json new file mode 100644 index 00000000000..f7abd91f512 --- /dev/null +++ b/exchange/exchangetest/ccpa-nosale-any-bidder.json @@ -0,0 +1,75 @@ +{ + "enforceCcpa": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "us_privacy": "1-Y-" + } + }, + "ext": { + "prebid": { + "nosale": ["*"] + } + }, + "user": { + "buyeruid": "some-buyer-id" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "us_privacy": "1-Y-" + } + }, + "ext": { + "prebid": { + "nosale": ["*"] + } + }, + "user": { + "buyeruid": "some-buyer-id" + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/ccpa-nosale-specific-bidder.json b/exchange/exchangetest/ccpa-nosale-specific-bidder.json new file mode 100644 index 00000000000..b89e29aea01 --- /dev/null +++ b/exchange/exchangetest/ccpa-nosale-specific-bidder.json @@ -0,0 +1,75 @@ +{ + "enforceCcpa": true, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "us_privacy": "1-Y-" + } + }, + "ext": { + "prebid": { + "nosale": ["appnexus"] + } + }, + "user": { + "buyeruid": "some-buyer-id" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "us_privacy": "1-Y-" + } + }, + "ext": { + "prebid": { + "nosale": ["appnexus"] + } + }, + "user": { + "buyeruid": "some-buyer-id" + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index 22b28adcacb..1e49b7acc6a 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -19,6 +19,8 @@ import ( "github.com/prebid/prebid-server/privacy/lmt" ) +const unknownBidder string = "" + func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) @@ -65,31 +67,32 @@ func cleanOpenRTBRequests(ctx context.Context, requestsByBidder, errs = splitBidRequest(orig, requestExt, impsByBidder, aliases, usersyncs, blables, labels) + if len(requestsByBidder) == 0 { + return + } + gdpr := extractGDPR(orig, usersyncIfAmbiguous) consent := extractConsent(orig) ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException() - var ccpaPolicy ccpa.Policy - if privacyConfig.CCPA.Enforce { - ccpaPolicy, _ = ccpa.ReadPolicy(orig) + ccpaEnforcer, err := extractCCPA(orig, privacyConfig, aliases) + if err != nil { + errs = append(errs, err) + return } - var lmtPolicy lmt.Policy - if privacyConfig.LMT.Enforce { - lmtPolicy = lmt.ReadPolicy(orig) - } + lmtEnforcer := extractLMT(orig, privacyConfig) // request level privacy policies privacyEnforcement := privacy.Enforcement{ - CCPA: ccpaPolicy.ShouldEnforce(), COPPA: orig.Regs != nil && orig.Regs.COPPA == 1, - LMT: lmtPolicy.ShouldEnforce(), + LMT: lmtEnforcer.ShouldEnforce(unknownBidder), } - privacyLabels.CCPAProvided = ccpaPolicy.Value != "" - privacyLabels.CCPAEnforced = privacyEnforcement.CCPA + privacyLabels.CCPAProvided = ccpaEnforcer.CanEnforce() + privacyLabels.CCPAEnforced = ccpaEnforcer.ShouldEnforce(unknownBidder) privacyLabels.COPPAEnforced = privacyEnforcement.COPPA - privacyLabels.LMTEnforced = privacyEnforcement.LMT + privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder) if gdpr == 1 { privacyLabels.GDPREnforced = true @@ -102,7 +105,10 @@ func cleanOpenRTBRequests(ctx context.Context, // bidder level privacy policies for bidder, bidReq := range requestsByBidder { + // CCPA + privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidder.String()) + // GDPR if gdpr == 1 { coreBidder := resolveBidder(bidder.String(), aliases) @@ -121,6 +127,32 @@ func cleanOpenRTBRequests(ctx context.Context, return } +func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases map[string]string) (privacy.PolicyEnforcer, error) { + ccpaPolicy, err := ccpa.ReadFromRequest(orig) + if err != nil { + return privacy.NilPolicyEnforcer{}, err + } + + validBidders := GetValidBidders(aliases) + ccpaParsedPolicy, err := ccpaPolicy.Parse(validBidders) + if err != nil { + return privacy.NilPolicyEnforcer{}, err + } + + ccpaEnforcer := privacy.EnabledPolicyEnforcer{ + Enabled: privacyConfig.CCPA.Enforce, + PolicyEnforcer: ccpaParsedPolicy, + } + return ccpaEnforcer, nil +} + +func extractLMT(orig *openrtb.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { + return privacy.EnabledPolicyEnforcer{ + Enabled: privacyConfig.LMT.Enforce, + PolicyEnforcer: lmt.ReadFromRequest(orig), + } +} + func splitBidRequest(req *openrtb.BidRequest, requestExt *openrtb_ext.ExtRequest, impsByBidder map[string][]openrtb.Imp, @@ -429,6 +461,20 @@ func parseAliases(orig *openrtb.BidRequest) (map[string]string, []error) { return aliases, nil } +func GetValidBidders(aliases map[string]string) map[string]struct{} { + validBidders := make(map[string]struct{}) + + for _, v := range openrtb_ext.BidderMap { + validBidders[v.String()] = struct{}{} + } + + for k := range aliases { + validBidders[k] = struct{}{} + } + + return validBidders +} + // Quick little randomizer for a list of strings. Stuffing it in utils to keep other files clean func randomizeList(list []openrtb_ext.BidderName) { l := len(list) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 528e875ab16..0dd6c0311ab 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -3,11 +3,13 @@ package exchange import ( "context" "encoding/json" + "errors" "fmt" "testing" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/stretchr/testify/assert" @@ -93,6 +95,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { func TestCleanOpenRTBRequestsCCPA(t *testing.T) { testCases := []struct { description string + reqExt json.RawMessage ccpaConsent string enforceCCPA bool expectDataScrub bool @@ -118,13 +121,46 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { CCPAEnforced: false, }, }, + { + description: "Feature Flag Enabled - No Sale Star - Doesn't Scrub", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), + ccpaConsent: "1-Y-", + enforceCCPA: true, + expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: true, + CCPAEnforced: false, + }, + }, + { + description: "Feature Flag Enabled - No Sale Specific Bidder - Doesn't Scrub", + reqExt: json.RawMessage(`{"prebid":{"nosale":["appnexus"]}}`), + ccpaConsent: "1-Y-", + enforceCCPA: true, + expectDataScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: true, + CCPAEnforced: true, + }, + }, + { + description: "Feature Flag Enabled - No Sale Different Bidder - Scrubs", + reqExt: json.RawMessage(`{"prebid":{"nosale":["rubicon"]}}`), + ccpaConsent: "1-Y-", + enforceCCPA: true, + expectDataScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + CCPAProvided: true, + CCPAEnforced: true, + }, + }, { description: "Feature Flag Disabled", ccpaConsent: "1-Y-", enforceCCPA: false, expectDataScrub: false, expectPrivacyLabels: pbsmetrics.PrivacyLabels{ - CCPAProvided: false, + CCPAProvided: true, CCPAEnforced: false, }, }, @@ -132,6 +168,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) + req.Ext = test.reqExt req.Regs = &openrtb.Regs{ Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), } @@ -157,6 +194,47 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { } } +func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { + testCases := []struct { + description string + reqExt json.RawMessage + reqRegsExt json.RawMessage + expectError error + }{ + { + description: "Invalid Consent", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), + reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), + expectError: &errortypes.InvalidPrivacyConsent{"request.regs.ext.us_privacy must contain 4 characters"}, + }, + { + description: "Invalid No Sale Bidders", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), + reqRegsExt: json.RawMessage(`{"us_privacy":"1NYN"}`), + expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Ext = test.reqExt + req.Regs = &openrtb.Regs{Ext: test.reqRegsExt} + + var reqExtStruct openrtb_ext.ExtRequest + err := json.Unmarshal(req.Ext, &reqExtStruct) + assert.NoError(t, err, test.description+":marshal_ext") + + privacyConfig := config.Privacy{ + CCPA: config.CCPA{ + Enforce: true, + }, + } + _, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &reqExtStruct, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + + assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) + } +} + func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { testCases := []struct { description string diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 42ac9d9d4b9..894be6763c6 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -24,6 +24,11 @@ type ExtRequestPrebid struct { Targeting *ExtRequestTargeting `json:"targeting,omitempty"` SupportDeals bool `json:"supportdeals,omitempty"` Debug bool `json:"debug,omitempty"` + + // NoSale specifies bidders with whom the publisher has a legal relationship where the + // passing of personally identifiable information doesn't constitute a sale per CCPA law. + // The array may contain a single sstar ('*') entry to represent all bidders. + NoSale []string `json:"nosale,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go new file mode 100644 index 00000000000..4856655402b --- /dev/null +++ b/privacy/ccpa/consentwriter.go @@ -0,0 +1,25 @@ +package ccpa + +import ( + "github.com/mxmCherry/openrtb" +) + +// ConsentWriter implements the PolicyWriter interface for CCPA. +type ConsentWriter struct { + Consent string +} + +// Write mutates an OpenRTB bid request with the CCPA consent string. +func (c ConsentWriter) Write(req *openrtb.BidRequest) error { + if req == nil { + return nil + } + + regs, err := buildRegs(c.Consent, req.Regs) + if err != nil { + return err + } + req.Regs = regs + + return nil +} diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go new file mode 100644 index 00000000000..57a7f8f4ddf --- /dev/null +++ b/privacy/ccpa/consentwriter_test.go @@ -0,0 +1,51 @@ +package ccpa + +import ( + "encoding/json" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/stretchr/testify/assert" +) + +func TestConsentWriter(t *testing.T) { + consent := "anyConsent" + testCases := []struct { + description string + request *openrtb.BidRequest + expected *openrtb.BidRequest + expectedError bool + }{ + { + description: "Nil Request", + request: nil, + expected: nil, + }, + { + description: "Success", + request: &openrtb.BidRequest{}, + expected: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + }, + }, + { + description: "Error With Regs.Ext - Does Not Mutate", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + expectedError: true, + expected: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + }, + } + + for _, test := range testCases { + writer := ConsentWriter{consent} + + err := writer.Write(test.request) + + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, test.request, test.description) + } +} diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go new file mode 100644 index 00000000000..3c934e67822 --- /dev/null +++ b/privacy/ccpa/parsedpolicy.go @@ -0,0 +1,137 @@ +package ccpa + +import ( + "errors" + "fmt" + + "github.com/prebid/prebid-server/errortypes" +) + +const ( + ccpaVersion1 = '1' + ccpaYes = 'Y' + ccpaNo = 'N' + ccpaNotApplicable = '-' +) + +const ( + indexVersion = 0 + indexExplicitNotice = 1 + indexOptOutSale = 2 + indexLSPACoveredTransaction = 3 +) + +const allBiddersMarker = "*" + +// ValidateConsent returns true if the consent string is empty or valid per the IAB CCPA spec. +func ValidateConsent(consent string) bool { + _, err := parseConsent(consent) + return err == nil +} + +// ParsedPolicy represents parsed and validated CCPA regulatory information. Use this struct +// to make enforcement decisions. +type ParsedPolicy struct { + consentSpecified bool + consentOptOutSale bool + noSaleForAllBidders bool + noSaleSpecificBidders map[string]struct{} +} + +// Parse returns a parsed and validated ParsedPolicy intended for use in enforcement decisions. +func (p Policy) Parse(validBidders map[string]struct{}) (ParsedPolicy, error) { + consentOptOut, err := parseConsent(p.Consent) + if err != nil { + msg := fmt.Sprintf("request.regs.ext.us_privacy %s", err.Error()) + return ParsedPolicy{}, &errortypes.InvalidPrivacyConsent{Message: msg} + } + + noSaleForAllBidders, noSaleSpecificBidders, err := parseNoSaleBidders(p.NoSaleBidders, validBidders) + if err != nil { + return ParsedPolicy{}, fmt.Errorf("request.ext.prebid.nosale is invalid: %s", err.Error()) + } + + return ParsedPolicy{ + consentSpecified: p.Consent != "", + consentOptOutSale: consentOptOut, + noSaleForAllBidders: noSaleForAllBidders, + noSaleSpecificBidders: noSaleSpecificBidders, + }, nil +} + +func parseConsent(consent string) (consentOptOutSale bool, err error) { + if consent == "" { + return false, nil + } + + if len(consent) != 4 { + return false, errors.New("must contain 4 characters") + } + + if consent[indexVersion] != ccpaVersion1 { + return false, errors.New("must specify version 1") + } + + var c byte + + c = consent[indexExplicitNotice] + if c != ccpaNo && c != ccpaYes && c != ccpaNotApplicable { + return false, errors.New("must specify 'N', 'Y', or '-' for the explicit notice") + } + + c = consent[indexOptOutSale] + if c != ccpaNo && c != ccpaYes && c != ccpaNotApplicable { + return false, errors.New("must specify 'N', 'Y', or '-' for the opt-out sale") + } + + c = consent[indexLSPACoveredTransaction] + if c != ccpaNo && c != ccpaYes && c != ccpaNotApplicable { + return false, errors.New("must specify 'N', 'Y', or '-' for the limited service provider agreement") + } + + return consent[indexOptOutSale] == ccpaYes, nil +} + +func parseNoSaleBidders(noSaleBidders []string, validBidders map[string]struct{}) (noSaleForAllBidders bool, noSaleSpecificBidders map[string]struct{}, err error) { + noSaleSpecificBidders = make(map[string]struct{}) + + if len(noSaleBidders) == 1 && noSaleBidders[0] == allBiddersMarker { + noSaleForAllBidders = true + return + } + + for _, bidder := range noSaleBidders { + if bidder == allBiddersMarker { + err = errors.New("can only specify all bidders if no other bidders are provided") + return + } + + if _, exists := validBidders[bidder]; exists { + noSaleSpecificBidders[bidder] = struct{}{} + } else { + err = fmt.Errorf("unrecognized bidder '%s'", bidder) + return + } + } + + return +} + +// CanEnforce returns true when consent is specifically provided by the publisher, as opposed to an empty string. +func (p ParsedPolicy) CanEnforce() bool { + return p.consentSpecified +} + +func (p ParsedPolicy) isNoSaleForBidder(bidder string) bool { + if p.noSaleForAllBidders { + return true + } + + _, exists := p.noSaleSpecificBidders[bidder] + return exists +} + +// ShouldEnforce returns true when the opt-out signal is explicitly detected. +func (p ParsedPolicy) ShouldEnforce(bidder string) bool { + return !p.isNoSaleForBidder(bidder) && p.consentOptOutSale +} diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go new file mode 100644 index 00000000000..2f7e8bfd683 --- /dev/null +++ b/privacy/ccpa/parsedpolicy_test.go @@ -0,0 +1,391 @@ +package ccpa + +import ( + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestValidateConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expected bool + }{ + { + description: "Empty String", + consent: "", + expected: true, + }, + { + description: "Valid Consent With Opt Out", + consent: "1NYN", + expected: true, + }, + { + description: "Valid Consent Without Opt Out", + consent: "1NNN", + expected: true, + }, + { + description: "Invalid", + consent: "malformed", + expected: false, + }, + } + + for _, test := range testCases { + result := ValidateConsent(test.consent) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestParse(t *testing.T) { + validBidders := map[string]struct{}{"a": {}} + + testCases := []struct { + description string + consent string + noSaleBidders []string + expectedPolicy ParsedPolicy + expectedError string + }{ + { + description: "Consent Error", + consent: "malformed", + noSaleBidders: []string{}, + expectedPolicy: ParsedPolicy{}, + expectedError: "request.regs.ext.us_privacy must contain 4 characters", + }, + { + description: "No Sale Error", + consent: "1NYN", + noSaleBidders: []string{"b"}, + expectedPolicy: ParsedPolicy{}, + expectedError: "request.ext.prebid.nosale is invalid: unrecognized bidder 'b'", + }, + { + description: "Success", + consent: "1NYN", + noSaleBidders: []string{"a"}, + expectedPolicy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: true, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{"a": {}}, + }, + }, + } + + for _, test := range testCases { + policy := Policy{test.consent, test.noSaleBidders} + + result, err := policy.Parse(validBidders) + + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + + assert.Equal(t, test.expectedPolicy, result, test.description) + } +} + +func TestParseConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expectedResult bool + expectedError string + }{ + { + description: "Valid", + consent: "1NYN", + expectedResult: true, + }, + { + description: "Valid - Not Sale", + consent: "1NNN", + expectedResult: false, + }, + { + description: "Valid - Not Applicable", + consent: "1---", + expectedResult: false, + }, + { + description: "Valid - Empty", + consent: "", + expectedResult: false, + }, + { + description: "Wrong Length", + consent: "1NY", + expectedResult: false, + expectedError: "must contain 4 characters", + }, + { + description: "Wrong Version", + consent: "2---", + expectedResult: false, + expectedError: "must specify version 1", + }, + { + description: "Explicit Notice Char", + consent: "1X--", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + }, + { + description: "Invalid Explicit Notice Case", + consent: "1y--", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + }, + { + description: "Invalid Opt-Out Sale Char", + consent: "1-X-", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + }, + { + description: "Invalid Opt-Out Sale Case", + consent: "1-y-", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + }, + { + description: "Invalid LSPA Char", + consent: "1--X", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + }, + { + description: "Invalid LSPA Case", + consent: "1--y", + expectedResult: false, + expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + }, + } + + for _, test := range testCases { + result, err := parseConsent(test.consent) + + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + + assert.Equal(t, test.expectedResult, result, test.description) + } +} + +func TestParseNoSaleBidders(t *testing.T) { + testCases := []struct { + description string + noSaleBidders []string + validBidders []string + expectedNoSaleForAllBidders bool + expectedNoSaleSpecificBidders map[string]struct{} + expectedError string + }{ + { + description: "Valid - No Bidders", + noSaleBidders: []string{}, + validBidders: []string{"a"}, + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{}, + }, + { + description: "Valid - 1 Bidder", + noSaleBidders: []string{"a"}, + validBidders: []string{"a"}, + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{"a": {}}, + }, + { + description: "Valid - 1+ Bidders", + noSaleBidders: []string{"a", "b"}, + validBidders: []string{"a", "b"}, + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{"a": {}, "b": {}}, + }, + { + description: "Valid - All Bidders", + noSaleBidders: []string{"*"}, + validBidders: []string{"a"}, + expectedNoSaleForAllBidders: true, + expectedNoSaleSpecificBidders: map[string]struct{}{}, + }, + { + description: "Bidder Not Valid", + noSaleBidders: []string{"b"}, + validBidders: []string{"a"}, + expectedError: "unrecognized bidder 'b'", + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{}, + }, + { + description: "All Bidder Mixed With Other Bidders Is Invalid", + noSaleBidders: []string{"*", "a"}, + validBidders: []string{"a"}, + expectedError: "can only specify all bidders if no other bidders are provided", + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{}, + }, + { + description: "Valid Bidders Case Sensitive", + noSaleBidders: []string{"a"}, + validBidders: []string{"A"}, + expectedError: "unrecognized bidder 'a'", + expectedNoSaleForAllBidders: false, + expectedNoSaleSpecificBidders: map[string]struct{}{}, + }, + } + + for _, test := range testCases { + validBiddersMap := make(map[string]struct{}) + for _, v := range test.validBidders { + validBiddersMap[v] = struct{}{} + } + + resultNoSaleForAllBidders, resultNoSaleSpecificBidders, err := parseNoSaleBidders(test.noSaleBidders, validBiddersMap) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expectedNoSaleForAllBidders, resultNoSaleForAllBidders, test.description+":allBidders") + assert.Equal(t, test.expectedNoSaleSpecificBidders, resultNoSaleSpecificBidders, test.description+":specificBidders") + } +} + +func TestCanEnforce(t *testing.T) { + testCases := []struct { + description string + policy ParsedPolicy + expected bool + }{ + { + description: "Specified", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: false, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{}, + }, + expected: true, + }, + { + description: "Not Specified", + policy: ParsedPolicy{ + consentSpecified: false, + consentOptOutSale: false, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{}, + }, + expected: false, + }, + } + + for _, test := range testCases { + result := test.policy.CanEnforce() + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestShouldEnforce(t *testing.T) { + testCases := []struct { + description string + policy ParsedPolicy + bidder string + expected bool + }{ + { + description: "Not Enforced - All Bidders No Sale", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: true, + noSaleForAllBidders: true, + noSaleSpecificBidders: map[string]struct{}{}, + }, + bidder: "a", + expected: false, + }, + { + description: "Not Enforced - Specific Bidders No Sale", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: true, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{"a": {}}, + }, + bidder: "a", + expected: false, + }, + { + description: "Not Enforced - No Bidder No Sale", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: false, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{}, + }, + bidder: "a", + expected: false, + }, + { + description: "Not Enforced - No Sale Case Sensitive", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: false, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{"A": {}}, + }, + bidder: "a", + expected: false, + }, + { + description: "Enforced - No Bidder No Sale", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: true, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{}, + }, + bidder: "a", + expected: true, + }, + { + description: "Enforced - No Sale Case Sensitive", + policy: ParsedPolicy{ + consentSpecified: true, + consentOptOutSale: true, + noSaleForAllBidders: false, + noSaleSpecificBidders: map[string]struct{}{"A": {}}, + }, + bidder: "a", + expected: true, + }, + } + + for _, test := range testCases { + result := test.policy.ShouldEnforce(test.bidder) + assert.Equal(t, test.expected, result, test.description) + } +} + +type mockPolicWriter struct { + mock.Mock +} + +func (m *mockPolicWriter) Write(req *openrtb.BidRequest) error { + args := m.Called(req) + return args.Error(0) +} diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 11ac434595a..a9f1c49e47d 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -9,139 +9,190 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -// Policy represents the CCPA regulation for an OpenRTB bid request. +// Policy represents the CCPA regulatory information from an OpenRTB bid request. type Policy struct { - Value string + Consent string + NoSaleBidders []string } -// ReadPolicy extracts the CCPA regulation policy from an OpenRTB request. -func ReadPolicy(req *openrtb.BidRequest) (Policy, error) { - policy := Policy{} +// ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request. +func ReadFromRequest(req *openrtb.BidRequest) (Policy, error) { + var consent string + var noSaleBidders []string - if req != nil && req.Regs != nil && len(req.Regs.Ext) > 0 { + if req == nil { + return Policy{}, nil + } + + // Read consent from request.regs.ext + if req.Regs != nil && len(req.Regs.Ext) > 0 { var ext openrtb_ext.ExtRegs if err := json.Unmarshal(req.Regs.Ext, &ext); err != nil { - return policy, err + return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) } - policy.Value = ext.USPrivacy + consent = ext.USPrivacy } - return policy, nil + // Read no sale bidders from request.ext.prebid + if len(req.Ext) > 0 { + var ext openrtb_ext.ExtRequest + if err := json.Unmarshal(req.Ext, &ext); err != nil { + return Policy{}, fmt.Errorf("error reading request.ext.prebid: %s", err) + } + noSaleBidders = ext.Prebid.NoSale + } + + return Policy{consent, noSaleBidders}, nil } -// Write mutates an OpenRTB bid request with the context of the CCPA policy. +// Write mutates an OpenRTB bid request with the CCPA regulatory information. func (p Policy) Write(req *openrtb.BidRequest) error { - if p.Value == "" { - return clearPolicy(req) - } - if req == nil { return nil } - if req.Regs == nil { - req.Regs = &openrtb.Regs{} + regs, err := buildRegs(p.Consent, req.Regs) + if err != nil { + return err } - - if req.Regs.Ext == nil { - ext, err := json.Marshal(openrtb_ext.ExtRegs{USPrivacy: p.Value}) - if err == nil { - req.Regs.Ext = ext - } + ext, err := buildExt(p.NoSaleBidders, req.Ext) + if err != nil { return err } - var extMap map[string]interface{} - err := json.Unmarshal(req.Regs.Ext, &extMap) - if err == nil { - extMap["us_privacy"] = p.Value - ext, err := json.Marshal(extMap) - if err == nil { - req.Regs.Ext = ext - } + req.Regs = regs + req.Ext = ext + return nil +} + +func buildRegs(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { + if consent == "" { + return buildRegsClear(regs) } - return err + return buildRegsWrite(consent, regs) } -func clearPolicy(req *openrtb.BidRequest) error { - if req == nil { - return nil +func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { + if regs == nil || len(regs.Ext) == 0 { + return regs, nil } - if req.Regs == nil { - return nil + var extMap map[string]interface{} + if err := json.Unmarshal(regs.Ext, &extMap); err != nil { + return nil, err } - if len(req.Regs.Ext) == 0 { - return nil + delete(extMap, "us_privacy") + + // Remove entire ext if it's now empty + if len(extMap) == 0 { + regsResult := *regs + regsResult.Ext = nil + return ®sResult, nil } - var extMap map[string]interface{} - err := json.Unmarshal(req.Regs.Ext, &extMap) + // Marshal ext if there are still other fields + var regsResult openrtb.Regs + ext, err := json.Marshal(extMap) if err == nil { - delete(extMap, "us_privacy") - if len(extMap) == 0 { - req.Regs.Ext = nil - } else { - ext, err := json.Marshal(extMap) - if err == nil { - req.Regs.Ext = ext - } - return err - } + regsResult = *regs + regsResult.Ext = ext } - - return err + return ®sResult, err } -// Validate returns an error if the CCPA policy does not adhere to the IAB spec. -func (p Policy) Validate() error { - if err := ValidateConsent(p.Value); err != nil { - return fmt.Errorf("request.regs.ext.us_privacy %s", err.Error()) +func buildRegsWrite(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { + if regs == nil { + return marshalRegsExt(openrtb.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) } - return nil + if regs.Ext == nil { + return marshalRegsExt(*regs, openrtb_ext.ExtRegs{USPrivacy: consent}) + } + + var extMap map[string]interface{} + if err := json.Unmarshal(regs.Ext, &extMap); err != nil { + return nil, err + } + + extMap["us_privacy"] = consent + return marshalRegsExt(*regs, extMap) } -// ValidateConsent returns an error if the CCPA consent string does not adhere to the IAB spec. -func ValidateConsent(consent string) error { - if consent == "" { - return nil +func marshalRegsExt(regs openrtb.Regs, ext interface{}) (*openrtb.Regs, error) { + extJSON, err := json.Marshal(ext) + if err == nil { + regs.Ext = extJSON } + return ®s, err +} - if len(consent) != 4 { - return errors.New("must contain 4 characters") +func buildExt(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { + if len(noSaleBidders) == 0 { + return buildExtClear(ext) } + return buildExtWrite(noSaleBidders, ext) +} - if consent[0] != '1' { - return errors.New("must specify version 1") +func buildExtClear(ext json.RawMessage) (json.RawMessage, error) { + if len(ext) == 0 { + return ext, nil } - var c byte + var extMap map[string]interface{} + if err := json.Unmarshal(ext, &extMap); err != nil { + return nil, err + } - c = consent[1] - if c != 'N' && c != 'Y' && c != '-' { - return errors.New("must specify 'N', 'Y', or '-' for the explicit notice") + prebidExt, exists := extMap["prebid"] + if !exists { + return ext, nil } - c = consent[2] - if c != 'N' && c != 'Y' && c != '-' { - return errors.New("must specify 'N', 'Y', or '-' for the opt-out sale") + // Verify prebid is an object + prebidExtMap, ok := prebidExt.(map[string]interface{}) + if !ok { + return nil, errors.New("request.ext.prebid is not a json object") } - c = consent[3] - if c != 'N' && c != 'Y' && c != '-' { - return errors.New("must specify 'N', 'Y', or '-' for the limited service provider agreement") + // Remove no sale member + delete(prebidExtMap, "nosale") + if len(prebidExtMap) == 0 { + delete(extMap, "prebid") } - return nil + // Remove entire ext if it's empty + if len(extMap) == 0 { + return nil, nil + } + + return json.Marshal(extMap) } -// ShouldEnforce returns true when the opt-out signal is explicitly detected. -func (p Policy) ShouldEnforce() bool { - if err := p.Validate(); err != nil { - return false +func buildExtWrite(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { + if len(ext) == 0 { + return json.Marshal(openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{NoSale: noSaleBidders}}) + } + + var extMap map[string]interface{} + if err := json.Unmarshal(ext, &extMap); err != nil { + return nil, err + } + + var prebidExt map[string]interface{} + if prebidExtInterface, exists := extMap["prebid"]; exists { + // Reference Existing Prebid Ext Map + if prebidExtMap, ok := prebidExtInterface.(map[string]interface{}); ok { + prebidExt = prebidExtMap + } else { + return nil, errors.New("request.ext.prebid is not a json object") + } + } else { + // Create New Empty Prebid Ext Map + prebidExt = make(map[string]interface{}) + extMap["prebid"] = prebidExt } - return p.Value != "" && p.Value[2] == 'Y' + prebidExt["nosale"] = noSaleBidders + return json.Marshal(extMap) } diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index e9b4c4525b1..7ff896e9ebf 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRead(t *testing.T) { +func TestReadFromRequest(t *testing.T) { testCases := []struct { description string request *openrtb.BidRequest @@ -18,83 +18,146 @@ func TestRead(t *testing.T) { { description: "Success", request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"ABC"}`), - }, + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ - Value: "ABC", + Consent: "ABC", + NoSaleBidders: []string{"a", "b"}, }, }, { - description: "Empty - No Request", + description: "Nil Request", request: nil, expectedPolicy: Policy{ - Value: "", + Consent: "", + NoSaleBidders: nil, }, }, { - description: "Empty - No Regs", + description: "Nil Regs", request: &openrtb.BidRequest{ Regs: nil, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ - Value: "", + Consent: "", + NoSaleBidders: []string{"a", "b"}, }, }, { - description: "Empty - No Ext", + description: "Nil Regs.Ext", request: &openrtb.BidRequest{ Regs: &openrtb.Regs{}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ - Value: "", + Consent: "", + NoSaleBidders: []string{"a", "b"}, }, }, { - description: "Empty - No Value", + description: "Empty Regs.Ext", request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"anythingElse":"42"}`), - }, + Regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ - Value: "", + Consent: "", + NoSaleBidders: []string{"a", "b"}, }, }, { - description: "Serialization Issue", + description: "Missing Regs.Ext USPrivacy Value", request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`malformed`), - }, + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"anythingElse":"42"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedPolicy: Policy{ + Consent: "", + NoSaleBidders: []string{"a", "b"}, + }, + }, + { + description: "Malformed Regs.Ext", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedError: true, + }, + { + description: "Invalid Regs.Ext Type", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + }, + expectedError: true, + }, + { + description: "Nil Ext", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: nil, + }, + expectedPolicy: Policy{ + Consent: "ABC", + NoSaleBidders: nil, + }, + }, + { + description: "Empty Ext", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{}`), + }, + expectedPolicy: Policy{ + Consent: "ABC", + NoSaleBidders: nil, + }, + }, + { + description: "Missing Ext.Prebid No Sale Value", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"anythingElse":"42"}`), + }, + expectedPolicy: Policy{ + Consent: "ABC", + NoSaleBidders: nil, + }, + }, + { + description: "Malformed Ext", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`malformed`), + }, + expectedError: true, + }, + { + description: "Invalid Ext.Prebid.NoSale Type", + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":"wrongtype"}}`), }, expectedError: true, }, { description: "Injection Attack", request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }, + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, }, expectedPolicy: Policy{ - Value: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", }, }, } for _, test := range testCases { - - p, e := ReadPolicy(test.request) - - if test.expectedError { - assert.Error(t, e, test.description) - } else { - assert.NoError(t, e, test.description) - } - - assert.Equal(t, test.expectedPolicy, p, test.description) + result, err := ReadFromRequest(test.request) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expectedPolicy, result, test.description) } } @@ -107,313 +170,422 @@ func TestWrite(t *testing.T) { expectedError bool }{ { - description: "Disabled", - policy: Policy{Value: ""}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{}, - }, - { - description: "Disabled - Nil Request", - policy: Policy{Value: ""}, + description: "Nil Request", + policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, request: nil, expected: nil, }, { - description: "Disabled - Empty Regs.Ext", - policy: Policy{Value: ""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, + description: "Success", + policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, + request: &openrtb.BidRequest{}, + expected: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + Ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + }, }, { - description: "Disabled - Remove From Request", - policy: Policy{Value: ""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, + description: "Error Regs.Ext - No Partial Update To Request", + policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, + request: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + expectedError: true, + expected: &openrtb.BidRequest{ + Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + }, }, { - description: "Disabled - Remove From Request, Leave Other req Values", - policy: Policy{Value: ""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - COPPA: 42, - Ext: json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - COPPA: 42}}, + description: "Error Ext - No Partial Update To Request", + policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, + request: &openrtb.BidRequest{ + Ext: json.RawMessage(`malformed}`), + }, + expectedError: true, + expected: &openrtb.BidRequest{ + Ext: json.RawMessage(`malformed}`), + }, }, + } + + for _, test := range testCases { + err := test.policy.Write(test.request) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, test.request, test.description) + } +} + +func TestBuildRegs(t *testing.T) { + testCases := []struct { + description string + consent string + regs *openrtb.Regs + expected *openrtb.Regs + expectedError bool + }{ { - description: "Disabled - Remove From Request, Leave Other req.ext Values", - policy: Policy{Value: ""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any","us_privacy":"toBeRemoved"}`)}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any"}`)}}, + description: "Clear", + consent: "", + regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"ABC"}`), + }, + expected: &openrtb.Regs{}, }, { - description: "Enabled - Nil Request", - policy: Policy{Value: "anyValue"}, - request: nil, - expected: nil, + description: "Clear - Error", + consent: "", + regs: &openrtb.Regs{ + Ext: json.RawMessage(`malformed`), + }, + expectedError: true, }, { - description: "Enabled With Nil Request Regs Object", - policy: Policy{Value: "anyValue"}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"anyValue"}`)}}, + description: "Write", + consent: "anyConsent", + regs: nil, + expected: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`), + }, }, { - description: "Enabled With Nil Request Regs Ext Object", - policy: Policy{Value: "anyValue"}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"anyValue"}`)}}, + description: "Write - Error", + consent: "anyConsent", + regs: &openrtb.Regs{ + Ext: json.RawMessage(`malformed`), + }, + expectedError: true, }, + } + + for _, test := range testCases { + result, err := buildRegs(test.consent, test.regs) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestBuildRegsClear(t *testing.T) { + testCases := []struct { + description string + regs *openrtb.Regs + expected *openrtb.Regs + expectedError bool + }{ { - description: "Enabled With Existing Request Regs Ext Object - Doesn't Overwrite", - policy: Policy{Value: "anyValue"}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any"}`)}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any","us_privacy":"anyValue"}`)}}, + description: "Nil Regs", + regs: nil, + expected: nil, }, { - description: "Enabled With Existing Request Regs Ext Object - Overwrites", - policy: Policy{Value: "anyValue"}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any","us_privacy":"toBeOverwritten"}`)}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any","us_privacy":"anyValue"}`)}}, + description: "Nil Regs.Ext", + regs: &openrtb.Regs{Ext: nil}, + expected: &openrtb.Regs{Ext: nil}, }, { - description: "Enabled With Existing Malformed Request Regs Ext Object", - policy: Policy{Value: "anyValue"}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`malformed`)}}, - expectedError: true, + description: "Empty Regs.Ext", + regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb.Regs{}, }, { - description: "Injection Attack With Nil Request Regs Object", - policy: Policy{Value: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }}, + description: "Removes Regs.Ext Entirely", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb.Regs{}, + }, + { + description: "Leaves Other Regs.Ext Values", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC", "other":"any"}`)}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, }, { - description: "Injection Attack With Nil Request Regs Ext Object", - policy: Policy{Value: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{}}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }}, + description: "Invalid Regs.Ext Type - Still Cleared", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb.Regs{}, }, { - description: "Injection Attack With Existing Request Regs Ext Object", - policy: Policy{Value: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any"}`), - }}, - expected: &openrtb.BidRequest{Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"existing":"any","us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }}, + description: "Malformed Regs.Ext", + regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + expectedError: true, }, } for _, test := range testCases { - err := test.policy.Write(test.request) - - if test.expectedError { - assert.Error(t, err, test.description) - } else { - assert.NoError(t, err, test.description) - assert.Equal(t, test.expected, test.request, test.description) - } + result, err := buildRegsClear(test.regs) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, result, test.description) } } -func TestValidate(t *testing.T) { +func TestBuildRegsWrite(t *testing.T) { testCases := []struct { description string - policy Policy - expectedError string + consent string + regs *openrtb.Regs + expected *openrtb.Regs + expectedError bool }{ { - description: "Valid", - policy: Policy{Value: "1NYN"}, - expectedError: "", + description: "Nil Regs", + consent: "anyConsent", + regs: nil, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { - description: "Valid - Not Applicable", - policy: Policy{Value: "1---"}, - expectedError: "", + description: "Nil Regs.Ext", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: nil}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { - description: "Valid - Empty", - policy: Policy{Value: ""}, - expectedError: "", + description: "Empty Regs.Ext", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Length", - policy: Policy{Value: "1NY"}, - expectedError: "request.regs.ext.us_privacy must contain 4 characters", + description: "Overwrites Existing", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Version", - policy: Policy{Value: "2---"}, - expectedError: "request.regs.ext.us_privacy must specify version 1", + description: "Leaves Other Ext Values", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Explicit Notice Char", - policy: Policy{Value: "1X--"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", + description: "Invalid Regs.Ext Type - Still Overwrites", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Explicit Notice Case", - policy: Policy{Value: "1y--"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the explicit notice", + description: "Malformed Regs.Ext", + consent: "anyConsent", + regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + expectedError: true, }, + } + + for _, test := range testCases { + result, err := buildRegsWrite(test.consent, test.regs) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestBuildExt(t *testing.T) { + testCases := []struct { + description string + noSaleBidders []string + ext json.RawMessage + expected json.RawMessage + expectedError bool + }{ { - description: "Invalid Opt-Out Sale Char", - policy: Policy{Value: "1-X-"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Clear - Nil", + noSaleBidders: nil, + ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + expected: nil, }, { - description: "Invalid Opt-Out Sale Case", - policy: Policy{Value: "1-y-"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Clear - Empty", + noSaleBidders: []string{}, + ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), + expected: nil, }, { - description: "Invalid LSPA Char", - policy: Policy{Value: "1--X"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Clear - Error", + noSaleBidders: []string{}, + ext: json.RawMessage(`malformed`), + expectedError: true, }, { - description: "Invalid LSPA Case", - policy: Policy{Value: "1--y"}, - expectedError: "request.regs.ext.us_privacy must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Write", + noSaleBidders: []string{"a", "b"}, + ext: nil, + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + }, + { + description: "Write - Error", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`malformed`), + expectedError: true, }, } for _, test := range testCases { - result := test.policy.Validate() - - if test.expectedError == "" { - assert.NoError(t, result, test.description) - } else { - assert.EqualError(t, result, test.expectedError, test.description) - } + result, err := buildExt(test.noSaleBidders, test.ext) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, result, test.description) } } -func TestValidateConsent(t *testing.T) { +func TestBuildExtClear(t *testing.T) { testCases := []struct { description string - consent string - expectedError string + ext json.RawMessage + expected json.RawMessage + expectedError bool }{ { - description: "Valid", - consent: "1NYN", - expectedError: "", + description: "Nil Ext", + ext: nil, + expected: nil, }, { - description: "Valid - Not Applicable", - consent: "1---", - expectedError: "", + description: "Empty Ext", + ext: json.RawMessage(``), + expected: json.RawMessage(``), }, { - description: "Invalid Empty", - consent: "", - expectedError: "", + description: "Empty Ext Object", + ext: json.RawMessage(`{}`), + expected: json.RawMessage(`{}`), }, { - description: "Invalid Length", - consent: "1NY", - expectedError: "must contain 4 characters", + description: "Empty Ext.Prebid", + ext: json.RawMessage(`{"prebid":{}}`), + expected: nil, }, { - description: "Invalid Version", - consent: "2---", - expectedError: "must specify version 1", + description: "Removes Ext Entirely", + ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + expected: nil, }, { - description: "Invalid Explicit Notice Char", - consent: "1X--", - expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + description: "Leaves Other Ext Values", + ext: json.RawMessage(`{"other":"any","prebid":{"nosale":["a","b"]}}`), + expected: json.RawMessage(`{"other":"any"}`), }, { - description: "Invalid Explicit Notice Case", - consent: "1y--", - expectedError: "must specify 'N', 'Y', or '-' for the explicit notice", + description: "Leaves Other Ext.Prebid Values", + ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), + expected: json.RawMessage(`{"prebid":{"other":"any"}}`), }, { - description: "Invalid Opt-Out Sale Char", - consent: "1-X-", - expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Leaves All Other Values", + ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), }, { - description: "Invalid Opt-Out Sale Case", - consent: "1-y-", - expectedError: "must specify 'N', 'Y', or '-' for the opt-out sale", + description: "Malformed Ext", + ext: json.RawMessage(`malformed`), + expectedError: true, }, { - description: "Invalid LSPA Char", - consent: "1--X", - expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Malformed Ext.Prebid", + ext: json.RawMessage(`{"prebid":malformed}`), + expectedError: true, }, { - description: "Invalid LSPA Case", - consent: "1--y", - expectedError: "must specify 'N', 'Y', or '-' for the limited service provider agreement", + description: "Invalid Ext.Prebid Type", + ext: json.RawMessage(`{"prebid":123}`), + expectedError: true, }, } for _, test := range testCases { - result := ValidateConsent(test.consent) - - if test.expectedError == "" { - assert.NoError(t, result, test.description) - } else { - assert.EqualError(t, result, test.expectedError, test.description) - } + result, err := buildExtClear(test.ext) + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, result, test.description) } } -func TestShouldEnforce(t *testing.T) { +func TestBuildExtWrite(t *testing.T) { testCases := []struct { - description string - policy Policy - expected bool + description string + noSaleBidders []string + ext json.RawMessage + expected json.RawMessage + expectedError bool }{ { - description: "Enforceable", - policy: Policy{Value: "1-Y-"}, - expected: true, + description: "Nil Ext", + noSaleBidders: []string{"a", "b"}, + ext: nil, + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + }, + { + description: "Empty Ext", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(``), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, { - description: "Not Enforceable - Not Present", - policy: Policy{Value: ""}, - expected: false, + description: "Empty Ext Object", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{}`), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, { - description: "Not Enforceable - Opt-Out Unknown", - policy: Policy{Value: "1---"}, - expected: false, + description: "Empty Ext.Prebid", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":{}}`), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, { - description: "Not Enforceable - Opt-Out Explicitly No", - policy: Policy{Value: "1-N-"}, - expected: false, + description: "Overwrites Existing", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":{"nosale":["x","y"]}}`), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, { - description: "Invalid", - policy: Policy{Value: "2---"}, - expected: false, + description: "Leaves Other Ext Values", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"other":"any"}`), + expected: json.RawMessage(`{"other":"any","prebid":{"nosale":["a","b"]}}`), + }, + { + description: "Leaves Other Ext.Prebid Values", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":{"other":"any"}}`), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), + }, + { + description: "Leaves All Other Values", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), + }, + { + description: "Invalid Ext.Prebid No Sale Type - Still Overrides", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":{"nosale":123}}`), + expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + }, + { + description: "Invalid Ext.Prebid Type ", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":"wrongtype"}`), + expectedError: true, + }, + { + description: "Malformed Ext", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{malformed`), + expectedError: true, + }, + { + description: "Malformed Ext.Prebid", + noSaleBidders: []string{"a", "b"}, + ext: json.RawMessage(`{"prebid":malformed}`), + expectedError: true, }, } for _, test := range testCases { - result := test.policy.ShouldEnforce() + result, err := buildExtWrite(test.noSaleBidders, test.ext) + assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } } + +func assertError(t *testing.T, expectError bool, err error, description string) { + t.Helper() + if expectError { + assert.Error(t, err, description) + } else { + assert.NoError(t, err, description) + } +} diff --git a/privacy/enforcer.go b/privacy/enforcer.go new file mode 100644 index 00000000000..0d5ecad5309 --- /dev/null +++ b/privacy/enforcer.go @@ -0,0 +1,43 @@ +package privacy + +// PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy. +type PolicyEnforcer interface { + // CanEnforce returns true when policy information is specifically provided by the publisher. + CanEnforce() bool + + // ShouldEnforce returns true when the OpenRTB request should have personally identifiable + // information (PII) removed or anonymized per the policy. + ShouldEnforce(bidder string) bool +} + +// NilPolicyEnforcer implements the PolicyEnforcer interface but will always return false. +type NilPolicyEnforcer struct{} + +// CanEnforce is hardcoded to always return false. +func (NilPolicyEnforcer) CanEnforce() bool { + return false +} + +// ShouldEnforce is hardcoded to always return false. +func (NilPolicyEnforcer) ShouldEnforce(bidder string) bool { + return false +} + +// EnabledPolicyEnforcer decorates a PolicyEnforcer with an enabled flag. +type EnabledPolicyEnforcer struct { + Enabled bool + PolicyEnforcer PolicyEnforcer +} + +// CanEnforce returns true when the PolicyEnforcer can enforce. +func (p EnabledPolicyEnforcer) CanEnforce() bool { + return p.PolicyEnforcer.CanEnforce() +} + +// ShouldEnforce returns true when the enforcer is enabled the PolicyEnforcer allows enforcement. +func (p EnabledPolicyEnforcer) ShouldEnforce(bidder string) bool { + if p.Enabled { + return p.PolicyEnforcer.ShouldEnforce(bidder) + } + return false +} diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go new file mode 100644 index 00000000000..b0c4032c714 --- /dev/null +++ b/privacy/enforcer_test.go @@ -0,0 +1,18 @@ +package privacy + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNilEnforcerCanEnforce(t *testing.T) { + nilEnforcer := &NilPolicyEnforcer{} + assert.False(t, nilEnforcer.CanEnforce()) +} + +func TestNilEnforcerShouldEnforce(t *testing.T) { + nilEnforcer := &NilPolicyEnforcer{} + assert.False(t, nilEnforcer.ShouldEnforce("")) + assert.False(t, nilEnforcer.ShouldEnforce("anyBidder")) +} diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go new file mode 100644 index 00000000000..040bbd6c94b --- /dev/null +++ b/privacy/gdpr/consentwriter.go @@ -0,0 +1,44 @@ +package gdpr + +import ( + "encoding/json" + + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb" +) + +// ConsentWriter implements the PolicyWriter interface for GDPR TCF. +type ConsentWriter struct { + Consent string +} + +// Write mutates an OpenRTB bid request with the GDPR TCF consent. +func (c ConsentWriter) Write(req *openrtb.BidRequest) error { + if c.Consent == "" { + return nil + } + + if req.User == nil { + req.User = &openrtb.User{} + } + + if req.User.Ext == nil { + ext, err := json.Marshal(openrtb_ext.ExtUser{Consent: c.Consent}) + if err == nil { + req.User.Ext = ext + } + return err + } + + var extMap map[string]interface{} + err := json.Unmarshal(req.User.Ext, &extMap) + if err == nil { + extMap["consent"] = c.Consent + ext, err := json.Marshal(extMap) + if err == nil { + req.User.Ext = ext + } + } + return err +} diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go new file mode 100644 index 00000000000..77fbdf88d92 --- /dev/null +++ b/privacy/gdpr/consentwriter_test.go @@ -0,0 +1,101 @@ +package gdpr + +import ( + "encoding/json" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/stretchr/testify/assert" +) + +func TestConsentWriter(t *testing.T) { + testCases := []struct { + description string + consent string + request *openrtb.BidRequest + expected *openrtb.BidRequest + expectedError bool + }{ + { + description: "Empty", + consent: "", + request: &openrtb.BidRequest{}, + expected: &openrtb.BidRequest{}, + }, + { + description: "Enabled With Nil Request User Object", + consent: "anyConsent", + request: &openrtb.BidRequest{}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + }, + { + description: "Enabled With Nil Request User Ext Object", + consent: "anyConsent", + request: &openrtb.BidRequest{User: &openrtb.User{}}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + }, + { + description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", + consent: "anyConsent", + request: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"existing":"any"}`)}}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, + }, + { + description: "Enabled With Existing Request User Ext Object - Overwrites", + consent: "anyConsent", + request: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, + }, + { + description: "Enabled With Existing Malformed Request User Ext Object", + consent: "anyConsent", + request: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`malformed`)}}, + expectedError: true, + }, + { + description: "Injection Attack With Nil Request User Object", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + request: &openrtb.BidRequest{}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + }}, + }, + { + description: "Injection Attack With Nil Request User Ext Object", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + request: &openrtb.BidRequest{User: &openrtb.User{}}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + }}, + }, + { + description: "Injection Attack With Existing Request User Ext Object", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + request: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"existing":"any"}`), + }}, + expected: &openrtb.BidRequest{User: &openrtb.User{ + Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), + }}, + }, + } + + for _, test := range testCases { + writer := ConsentWriter{test.consent} + err := writer.Write(test.request) + + if test.expectedError { + assert.Error(t, err, test.description) + } else { + assert.NoError(t, err, test.description) + assert.Equal(t, test.expected, test.request, test.description) + } + } +} diff --git a/privacy/gdpr/policy.go b/privacy/gdpr/policy.go index 4733e1edd38..0464a9ff979 100644 --- a/privacy/gdpr/policy.go +++ b/privacy/gdpr/policy.go @@ -1,10 +1,6 @@ package gdpr import ( - "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb" "github.com/prebid/go-gdpr/vendorconsent" ) @@ -14,38 +10,8 @@ type Policy struct { Consent string } -// Write mutates an OpenRTB bid request with the context of the GDPR policy. -func (p Policy) Write(req *openrtb.BidRequest) error { - if p.Consent == "" { - return nil - } - - if req.User == nil { - req.User = &openrtb.User{} - } - - if req.User.Ext == nil { - ext, err := json.Marshal(openrtb_ext.ExtUser{Consent: p.Consent}) - if err == nil { - req.User.Ext = ext - } - return err - } - - var extMap map[string]interface{} - err := json.Unmarshal(req.User.Ext, &extMap) - if err == nil { - extMap["consent"] = p.Consent - ext, err := json.Marshal(extMap) - if err == nil { - req.User.Ext = ext - } - } - return err -} - -// ValidateConsent returns an error if the GDPR consent string does not adhere to the IAB TCF spec. -func ValidateConsent(consent string) error { +// ValidateConsent returns true if the consent string is empty or valid per the IAB TCF spec. +func ValidateConsent(consent string) bool { _, err := vendorconsent.ParseString(consent) - return err + return err == nil } diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index c9bf10cd24a..dc8f56425c5 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -1,129 +1,36 @@ package gdpr import ( - "encoding/json" "testing" - "github.com/mxmCherry/openrtb" "github.com/stretchr/testify/assert" ) -func TestWrite(t *testing.T) { - testCases := []struct { - description string - policy Policy - request *openrtb.BidRequest - expected *openrtb.BidRequest - expectedError bool - }{ - { - description: "Disabled", - policy: Policy{Consent: ""}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{}, - }, - { - description: "Enabled With Nil Request User Object", - policy: Policy{Consent: "anyConsent"}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, - }, - { - description: "Enabled With Nil Request User Ext Object", - policy: Policy{Consent: "anyConsent"}, - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, - }, - { - description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", - policy: Policy{Consent: "anyConsent"}, - request: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"existing":"any"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, - }, - { - description: "Enabled With Existing Request User Ext Object - Overwrites", - policy: Policy{Consent: "anyConsent"}, - request: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, - }, - { - description: "Enabled With Existing Malformed Request User Ext Object", - policy: Policy{Consent: "anyConsent"}, - request: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`malformed`)}}, - expectedError: true, - }, - { - description: "Injection Attack With Nil Request User Object", - policy: Policy{Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }}, - }, - { - description: "Injection Attack With Nil Request User Ext Object", - policy: Policy{Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), - }}, - }, - { - description: "Injection Attack With Existing Request User Ext Object", - policy: Policy{Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}, - request: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"existing":"any"}`), - }}, - expected: &openrtb.BidRequest{User: &openrtb.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), - }}, - }, - } - - for _, test := range testCases { - err := test.policy.Write(test.request) - - if test.expectedError { - assert.Error(t, err, test.description) - } else { - assert.NoError(t, err, test.description) - assert.Equal(t, test.expected, test.request, test.description) - } - } -} - func TestValidateConsent(t *testing.T) { testCases := []struct { description string consent string - expectError bool + expected bool }{ { description: "Invalid", consent: "", - expectError: true, + expected: false, }, { - description: "Valid", + description: "TCF1 Valid", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expectError: false, + expected: true, + }, + { + description: "TCF2 Valid", + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + expected: true, }, } for _, test := range testCases { result := ValidateConsent(test.consent) - - if test.expectError { - assert.Error(t, result, test.description) - } else { - assert.NoError(t, result, test.description) - } + assert.Equal(t, test.expected, result, test.description) } } diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index 79425bf59f7..295dcc46469 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -15,19 +15,21 @@ type Policy struct { SignalProvided bool } -// ReadPolicy extracts the LMT (Limit Ad Tracking) policy from an OpenRTB bid request. -func ReadPolicy(req *openrtb.BidRequest) Policy { - policy := Policy{} - +// ReadFromRequest extracts the LMT (Limit Ad Tracking) policy from an OpenRTB bid request. +func ReadFromRequest(req *openrtb.BidRequest) (policy Policy) { if req != nil && req.Device != nil && req.Device.Lmt != nil { policy.Signal = int(*req.Device.Lmt) policy.SignalProvided = true } + return +} - return policy +// CanEnforce returns true the LMT (Limit Ad Tracking) signal is provided by the publisher. +func (p Policy) CanEnforce() bool { + return p.SignalProvided } // ShouldEnforce returns true when the LMT (Limit Ad Tracking) policy is in effect. -func (p Policy) ShouldEnforce() bool { +func (p Policy) ShouldEnforce(bidder string) bool { return p.SignalProvided && p.Signal == trackingRestricted } diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index 45de219a9bf..3027414fd02 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRead(t *testing.T) { +func TestReadFromRequest(t *testing.T) { var one int8 = 1 testCases := []struct { @@ -60,11 +60,73 @@ func TestRead(t *testing.T) { } for _, test := range testCases { - p := ReadPolicy(test.request) + p := ReadFromRequest(test.request) assert.Equal(t, test.expectedPolicy, p, test.description) } } +func TestCanEnforce(t *testing.T) { + testCases := []struct { + description string + policy Policy + expected bool + }{ + { + description: "Signal Not Provided - Zero", + policy: Policy{ + Signal: 0, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Not Provided - One", + policy: Policy{ + Signal: 1, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Not Provided - Other", + policy: Policy{ + Signal: 42, + SignalProvided: false, + }, + expected: false, + }, + { + description: "Signal Provided - Zero", + policy: Policy{ + Signal: 0, + SignalProvided: true, + }, + expected: true, + }, + { + description: "Signal Provided - One", + policy: Policy{ + Signal: 1, + SignalProvided: true, + }, + expected: true, + }, + { + description: "Signal Provided - Other", + policy: Policy{ + Signal: 42, + SignalProvided: true, + }, + expected: true, + }, + } + + for _, test := range testCases { + result := test.policy.CanEnforce() + assert.Equal(t, test.expected, result, test.description) + } +} + func TestShouldEnforce(t *testing.T) { testCases := []struct { description string @@ -122,7 +184,7 @@ func TestShouldEnforce(t *testing.T) { } for _, test := range testCases { - result := test.policy.ShouldEnforce() + result := test.policy.ShouldEnforce("") assert.Equal(t, test.expected, result, test.description) } } diff --git a/privacy/policies.go b/privacy/policies.go index cb11c6d03a6..bc844a4e463 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -1,60 +1,14 @@ package privacy import ( - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/privacy/ccpa" "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy/lmt" ) // Policies represents the privacy regulations for an OpenRTB bid request. type Policies struct { - GDPR gdpr.Policy CCPA ccpa.Policy -} - -type policyWriter interface { - Write(req *openrtb.BidRequest) error -} - -// Write mutates an OpenRTB bid request with the policies applied. -func (p Policies) Write(req *openrtb.BidRequest) error { - return writePolicies(req, []policyWriter{ - p.GDPR, p.CCPA, - }) -} - -func writePolicies(req *openrtb.BidRequest, writers []policyWriter) error { - for _, writer := range writers { - if err := writer.Write(req); err != nil { - return err - } - } - - return nil -} - -// ReadPoliciesFromConsent inspects the consent string kind and sets the corresponding values in a new Policies object. -func ReadPoliciesFromConsent(consent string) (Policies, bool) { - if len(consent) == 0 { - return Policies{}, false - } - - if err := gdpr.ValidateConsent(consent); err == nil { - return Policies{ - GDPR: gdpr.Policy{ - Consent: consent, - }, - }, true - } - - if err := ccpa.ValidateConsent(consent); err == nil { - return Policies{ - CCPA: ccpa.Policy{ - Value: consent, - }, - }, true - } - - return Policies{}, false + GDPR gdpr.Policy + LMT lmt.Policy } diff --git a/privacy/policies_test.go b/privacy/policies_test.go deleted file mode 100644 index 34fbe52d0e9..00000000000 --- a/privacy/policies_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package privacy - -import ( - "errors" - "testing" - - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestWritePoliciesNone(t *testing.T) { - request := &openrtb.BidRequest{} - policyWriters := []policyWriter{} - - err := writePolicies(request, policyWriters) - - assert.NoError(t, err) -} - -func TestWritePoliciesOne(t *testing.T) { - request := &openrtb.BidRequest{} - mockWriter := new(mockPolicyWriter) - policyWriters := []policyWriter{ - mockWriter, - } - - mockWriter.On("Write", request).Return(nil).Once() - - err := writePolicies(request, policyWriters) - - assert.NoError(t, err) - mockWriter.AssertExpectations(t) -} - -func TestWritePoliciesMany(t *testing.T) { - request := &openrtb.BidRequest{} - mockWriter1 := new(mockPolicyWriter) - mockWriter2 := new(mockPolicyWriter) - policyWriters := []policyWriter{ - mockWriter1, mockWriter2, - } - - mockWriter1.On("Write", request).Return(nil).Once() - mockWriter2.On("Write", request).Return(nil).Once() - - err := writePolicies(request, policyWriters) - - assert.NoError(t, err) - mockWriter1.AssertExpectations(t) - mockWriter2.AssertExpectations(t) -} - -func TestWritePoliciesError(t *testing.T) { - request := &openrtb.BidRequest{} - mockWriter := new(mockPolicyWriter) - policyWriters := []policyWriter{ - mockWriter, - } - - expectedErr := errors.New("anyError") - mockWriter.On("Write", request).Return(expectedErr).Once() - - err := writePolicies(request, policyWriters) - - assert.Error(t, err, expectedErr) - mockWriter.AssertExpectations(t) -} - -type mockPolicyWriter struct { - mock.Mock -} - -func (m *mockPolicyWriter) Write(req *openrtb.BidRequest) error { - args := m.Called(req) - return args.Error(0) -} - -func TestReadPoliciesFromConsent(t *testing.T) { - testCases := []struct { - description string - consent string - expectedResultValue Policies - expectedResultOK bool - }{ - { - description: "Empty String", - consent: "", - expectedResultValue: Policies{}, - expectedResultOK: false, - }, - { - description: "CCPA", - consent: "1NYN", - expectedResultValue: Policies{CCPA: ccpa.Policy{Value: "1NYN"}}, - expectedResultOK: true, - }, - { - description: "GDPR TCF 1.0", - consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expectedResultValue: Policies{GDPR: gdpr.Policy{Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY"}}, - expectedResultOK: true, - }, - { - description: "Invalid", - consent: "any invalid", - expectedResultValue: Policies{}, - expectedResultOK: false, - }, - } - - for _, test := range testCases { - resultValue, resultOK := ReadPoliciesFromConsent(test.consent) - assert.Equal(t, test.expectedResultValue, resultValue, test.description+":value") - assert.Equal(t, test.expectedResultOK, resultOK, test.description+":ok") - } -} diff --git a/privacy/writer.go b/privacy/writer.go new file mode 100644 index 00000000000..c61767f16c8 --- /dev/null +++ b/privacy/writer.go @@ -0,0 +1,18 @@ +package privacy + +import ( + "github.com/mxmCherry/openrtb" +) + +// PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. +type PolicyWriter interface { + Write(req *openrtb.BidRequest) error +} + +// NilPolicyWriter implements the PolicyWriter interface but performs no action. +type NilPolicyWriter struct{} + +// Write is hardcoded to perform no action with the OpenRTB bid request. +func (NilPolicyWriter) Write(req *openrtb.BidRequest) error { + return nil +} diff --git a/privacy/writer_test.go b/privacy/writer_test.go new file mode 100644 index 00000000000..79170cfc451 --- /dev/null +++ b/privacy/writer_test.go @@ -0,0 +1,25 @@ +package privacy + +import ( + "encoding/json" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/stretchr/testify/assert" +) + +func TestNilWriter(t *testing.T) { + request := &openrtb.BidRequest{ + ID: "anyID", + Ext: json.RawMessage(`{"anyJson":"anyValue"}`), + } + expectedRequest := &openrtb.BidRequest{ + ID: "anyID", + Ext: json.RawMessage(`{"anyJson":"anyValue"}`), + } + + nilWriter := &NilPolicyWriter{} + nilWriter.Write(request) + + assert.Equal(t, expectedRequest, request) +} From 472c7a00105cb986c072047dd416af299b7f7a1b Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 17 Sep 2020 11:05:24 -0400 Subject: [PATCH 203/603] Fix Merge Conflict (#1502) --- adapters/colossus/usersync_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/colossus/usersync_test.go b/adapters/colossus/usersync_test.go index 79d5483d528..52eb6389b1c 100644 --- a/adapters/colossus/usersync_test.go +++ b/adapters/colossus/usersync_test.go @@ -23,7 +23,7 @@ func TestColossusSyncer(t *testing.T) { Consent: "A", }, CCPA: ccpa.Policy{ - Value: "1-YY", + Consent: "1-YY", }, }) From 97be47dc634d8b2fbf70c15fa1d73dc7cbf9d05d Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Thu, 17 Sep 2020 08:29:14 -0700 Subject: [PATCH 204/603] Update conversant adapter for new prebid-server interface (#1484) --- adapters/conversant/cnvr_legacy.go | 291 ++++++ adapters/conversant/cnvr_legacy_test.go | 853 ++++++++++++++++++ adapters/conversant/conversant.go | 359 +++----- adapters/conversant/conversant_test.go | 849 +---------------- .../conversanttest/exemplary/banner.json | 113 +++ .../conversanttest/exemplary/simple_app.json | 114 +++ .../conversanttest/exemplary/video.json | 138 +++ .../supplemental/missing_cnvrext.json | 32 + .../supplemental/missing_ext.json | 30 + .../supplemental/missing_siteid.json | 35 + .../supplemental/server_badresponse.json | 57 ++ .../supplemental/server_nocontent.json | 51 ++ .../supplemental/server_unknownstatus.json | 55 ++ exchange/adapter_map.go | 3 +- openrtb_ext/imp_conversant.go | 13 + static/bidder-params/conversant.json | 4 - 16 files changed, 1908 insertions(+), 1089 deletions(-) create mode 100644 adapters/conversant/cnvr_legacy.go create mode 100644 adapters/conversant/cnvr_legacy_test.go create mode 100644 adapters/conversant/conversanttest/exemplary/banner.json create mode 100644 adapters/conversant/conversanttest/exemplary/simple_app.json create mode 100644 adapters/conversant/conversanttest/exemplary/video.json create mode 100644 adapters/conversant/conversanttest/supplemental/missing_cnvrext.json create mode 100644 adapters/conversant/conversanttest/supplemental/missing_ext.json create mode 100644 adapters/conversant/conversanttest/supplemental/missing_siteid.json create mode 100644 adapters/conversant/conversanttest/supplemental/server_badresponse.json create mode 100644 adapters/conversant/conversanttest/supplemental/server_nocontent.json create mode 100644 adapters/conversant/conversanttest/supplemental/server_unknownstatus.json create mode 100644 openrtb_ext/imp_conversant.go diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go new file mode 100644 index 00000000000..bd121f098c0 --- /dev/null +++ b/adapters/conversant/cnvr_legacy.go @@ -0,0 +1,291 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" +) + +type ConversantLegacyAdapter struct { + http *adapters.HTTPAdapter + URI string +} + +// Corresponds to the bidder name in cookies and requests +func (a *ConversantLegacyAdapter) Name() string { + return "conversant" +} + +// Return true so no request will be sent unless user has been sync'ed. +func (a *ConversantLegacyAdapter) SkipNoCookies() bool { + return true +} + +type conversantParams struct { + SiteID string `json:"site_id"` + Secure *int8 `json:"secure"` + TagID string `json:"tag_id"` + Position *int8 `json:"position"` + BidFloor float64 `json:"bidfloor"` + Mobile *int8 `json:"mobile"` + MIMEs []string `json:"mimes"` + API []int8 `json:"api"` + Protocols []int8 `json:"protocols"` + MaxDuration *int64 `json:"maxduration"` +} + +func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + + if err != nil { + return nil, err + } + + // Create a map of impression objects for both request creation + // and response parsing. + + impMap := make(map[string]*openrtb.Imp, len(cnvrReq.Imp)) + for idx := range cnvrReq.Imp { + impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] + } + + // Fill in additional info from custom params + + for _, unit := range bidder.AdUnits { + var params conversantParams + + imp := impMap[unit.Code] + if imp == nil { + // Skip ad units that do not have corresponding impressions. + continue + } + + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + // Fill in additional Site info + if params.SiteID != "" { + if cnvrReq.Site != nil { + cnvrReq.Site.ID = params.SiteID + } + if cnvrReq.App != nil { + cnvrReq.App.ID = params.SiteID + } + } + + if params.Mobile != nil && !(cnvrReq.Site == nil) { + cnvrReq.Site.Mobile = *params.Mobile + } + + // Fill in additional impression info + + imp.DisplayManager = "prebid-s2s" + imp.DisplayManagerVer = "1.0.1" + imp.BidFloor = params.BidFloor + imp.TagID = params.TagID + + var position *openrtb.AdPosition + if params.Position != nil { + position = openrtb.AdPosition(*params.Position).Ptr() + } + + if imp.Banner != nil { + imp.Banner.Pos = position + } else if imp.Video != nil { + imp.Video.Pos = position + + if len(params.API) > 0 { + imp.Video.API = make([]openrtb.APIFramework, 0, len(params.API)) + for _, api := range params.API { + imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) + } + } + + // Include protocols, mimes, and max duration if specified + // These properties can also be specified in ad unit's video object, + // but are overridden if the custom params object also contains them. + + if len(params.Protocols) > 0 { + imp.Video.Protocols = make([]openrtb.Protocol, 0, len(params.Protocols)) + for _, protocol := range params.Protocols { + imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + } + } + + if len(params.MIMEs) > 0 { + imp.Video.MIMEs = make([]string, len(params.MIMEs)) + copy(imp.Video.MIMEs, params.MIMEs) + } + + if params.MaxDuration != nil { + imp.Video.MaxDuration = *params.MaxDuration + } + } + + // Take care not to override the global secure flag + + if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { + imp.Secure = params.Secure + } + } + + // Do a quick check on required parameters + + if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing site id", + } + } + + if cnvrReq.App != nil && cnvrReq.App.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing app id", + } + } + + // Start capturing debug info + + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + } + + if cnvrReq.Device == nil { + cnvrReq.Device = &openrtb.Device{} + } + + // Convert request to json to be sent over http + + j, _ := json.Marshal(cnvrReq) + + if req.IsDebug { + debug.RequestBody = string(j) + bidder.Debug = append(bidder.Debug, debug) + } + + httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) + httpReq.Header.Add("Content-Type", "application/json") + httpReq.Header.Add("Accept", "application/json") + + resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if req.IsDebug { + debug.StatusCode = resp.StatusCode + } + + if resp.StatusCode == 204 { + return nil, nil + } + + body, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if resp.StatusCode != 200 { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if req.IsDebug { + debug.ResponseBody = string(body) + } + + var bidResp openrtb.BidResponse + + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: err.Error(), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, seatbid := range bidResp.SeatBid { + for _, bid := range seatbid.Bid { + if bid.Price <= 0 { + continue + } + + imp := impMap[bid.ImpID] + if imp == nil { + // All returned bids should have a matching impression + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), + } + } + + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbsBid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + Price: bid.Price, + Creative_id: bid.CrID, + BidderCode: bidder.BidderCode, + } + + if imp.Video != nil { + pbsBid.CreativeMediaType = "video" + pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl + pbsBid.Width = imp.Video.W + pbsBid.Height = imp.Video.H + } else { + pbsBid.CreativeMediaType = "banner" + pbsBid.NURL = bid.NURL + pbsBid.Adm = bid.AdM + pbsBid.Width = bid.W + pbsBid.Height = bid.H + } + + bids = append(bids, &pbsBid) + } + } + + if len(bids) == 0 { + return nil, nil + } + + return bids, nil +} + +func NewConversantAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { + a := adapters.NewHTTPAdapter(config) + + return &ConversantLegacyAdapter{ + http: a, + URI: uri, + } +} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go new file mode 100644 index 00000000000..b958e320dc7 --- /dev/null +++ b/adapters/conversant/cnvr_legacy_test.go @@ -0,0 +1,853 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" +) + +// Constants + +const ExpectedSiteID string = "12345" +const ExpectedDisplayManager string = "prebid-s2s" +const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" +const ExpectedNURL string = "http://test.dotomi.com" +const ExpectedAdM string = "" +const ExpectedCrID string = "98765" + +const DefaultParam = `{"site_id": "12345"}` + +// Test properties of Adapter interface + +func TestConversantProperties(t *testing.T) { + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + assertNotEqual(t, an.Name(), "", "Missing family name") + assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") +} + +// Test empty bid requests + +func TestConversantEmptyBid(t *testing.T) { + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{} + _, err := an.Call(ctx, &pbReq, &pbBidder) + assertTrue(t, err != nil, "No error received for an invalid request") +} + +// Test required parameters, which is just the site id for now + +func TestConversantRequiredParameters(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + ctx := context.TODO() + + testParams := func(params ...string) (pbs.PBSBidSlice, error) { + req, err := CreateBannerRequest(params...) + if err != nil { + return nil, err + } + return an.Call(ctx, req, req.Bidders[0]) + } + + var err error + + if _, err = testParams(`{}`); err == nil { + t.Fatal("Failed to catch missing site id") + } +} + +// Test handling of 404 + +func TestConversantBadStatus(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assertTrue(t, err != nil, "Failed to catch 404 error") +} + +// Test handling of HTTP timeout + +func TestConversantTimeout(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-time.After(2 * time.Millisecond) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + // Create a context that expires before http returns + + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + + // Create a basic request + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + // Attempt to process the request, which should hit a timeout + // immediately + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err == nil || err != context.DeadlineExceeded { + t.Fatal("No timeout recevied for timed out request", err) + } +} + +// Test handling of 204 + +func TestConversantNoBid(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if resp != nil || err != nil { + t.Fatal("Failed to handle empty bid", err) + } +} + +// Verify an outgoing openrtp request is created correctly + +func TestConversantRequestDefault(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, imp.TagID, "", "Request tag id") + assertTrue(t, imp.Banner.Pos == nil, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify inapp video request +func TestConversantInappVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + requestParam := `{"secure": 1, "site_id": "12345"}` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(requestParam) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + assertEqual(t, lastReq.App.ID, "12345", "App Id") +} + +// Verify inapp video request +func TestConversantInappBannerRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "secure": 1, + "site_id": "12345", + "tag_id": "top", + "position": 2, + "bidfloor": 1.01 }` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + + ctx := context.TODO() + pbReq, _ := CreateBannerRequest(param) + pbReq, err := ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, lastReq.App.ID, "12345", "App Id") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify an outgoing openrtp request with additional conversant parameters is +// processed correctly + +func TestConversantRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile": 1 }` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 1, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "top", "Request tag id") + assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify openrtp responses are converted correctly + +func TestConversantResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") + assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") + } + } +} + +// Test video request + +func TestConversantBasicVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") + assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") + assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") + assertTrue(t, imp.Video.API == nil, "Request video api should be nil") +} + +// Test video request with parameters in custom params object + +func TestConversantVideoRequestWithParams(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01, + "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "api": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") + assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") + assertEqual(t, imp.Video.API[0], openrtb.APIFramework(1), "Request video api 1") + assertEqual(t, imp.Video.API[1], openrtb.APIFramework(2), "Request video api 2") +} + +// Test video request with parameters in the video object + +func TestConversantVideoRequestWithParams2(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(param) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("Failed to convert to a video request", err) + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("Failed to parse video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") +} + +// Test video responses + +func TestConversantVideoResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, "", "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") + assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") + } + } +} + +// Helpers to create a banner and video requests + +func CreateRequest(params ...string) *pbs.PBSRequest { + num := len(params) + + req := pbs.PBSRequest{ + Tid: "t-000", + AccountID: "1", + AdUnits: make([]pbs.AdUnit, num), + } + + for i := 0; i < num; i++ { + req.AdUnits[i] = pbs.AdUnit{ + Code: fmt.Sprintf("au-%03d", i), + Sizes: []openrtb.Format{ + { + W: 300, + H: 250, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "conversant", + BidID: fmt.Sprintf("b-%03d", i), + Params: json.RawMessage(params[i]), + }, + }, + } + } + + return &req +} + +// Convert a request to a video request by adding required properties + +func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { + for i := 0; i < len(req.AdUnits); i++ { + video := pbs.PBSVideo{} + if i < len(videoParams) { + err := json.Unmarshal([]byte(videoParams[i]), &video) + if err != nil { + return nil, err + } + } + + if video.Mimes == nil { + video.Mimes = []string{"video/mp4"} + } + + req.AdUnits[i].Video = video + req.AdUnits[i].MediaTypes = []string{"video"} + } + + return req, nil +} + +// Convert a request to an app request by adding required properties +func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { + app := new(openrtb.App) + err := json.Unmarshal([]byte(appParams), &app) + if err == nil { + req.App = app + } + + return req, nil +} + +// Feed the request thru the prebid parser so user id and +// other private properties are defined + +func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { + body := new(bytes.Buffer) + _ = json.NewEncoder(body).Encode(req) + + // Need to pass the conversant user id thru uid cookie + + httpReq := httptest.NewRequest("POST", "/foo", body) + cookie := usersync.NewPBSCookie() + _ = cookie.TrySync("conversant", ExpectedBuyerUID) + httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) + httpReq.Header.Add("Referer", "http://example.com") + cache, _ := dummycache.New() + hcc := config.HostCookie{} + + parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cache, &hcc) + + return parsedReq, err +} + +// A helper to create a banner request + +func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ParseRequest(req) + return req, err +} + +// A helper to create a video request + +func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ConvertToVideoRequest(req) + if err != nil { + return nil, err + } + req, err = ParseRequest(req) + return req, err +} + +// Helper to create a test http server that receives and generate openrtb requests and responses + +func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { + var lastBidRequest openrtb.BidRequest + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var bidReq openrtb.BidRequest + var price float64 + var bids []openrtb.Bid + var bid openrtb.Bid + + err = json.Unmarshal(body, &bidReq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + lastBidRequest = bidReq + + for i, imp := range bidReq.Imp { + if i < len(prices) { + price = prices[i] + } else { + price = 0 + } + + if price > 0 { + bid = openrtb.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: price, + NURL: ExpectedNURL, + AdM: ExpectedAdM, + CrID: ExpectedCrID, + } + + if imp.Banner != nil { + bid.W = *imp.Banner.W + bid.H = *imp.Banner.H + } else if imp.Video != nil { + bid.W = imp.Video.W + bid.H = imp.Video.H + } + } else { + bid = openrtb.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: 0, + } + } + + bids = append(bids, bid) + } + + if len(bids) == 0 { + w.WriteHeader(http.StatusNoContent) + } else { + js, _ := json.Marshal(openrtb.BidResponse{ + ID: bidReq.ID, + SeatBid: []openrtb.SeatBid{ + { + Bid: bids, + }, + }, + }) + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(js) + } + }), + ) + + return server, &lastBidRequest +} + +// Helper to remove impressions with $0 bids + +func FilterZeroPrices(prices []float64, imps []openrtb.Imp) ([]float64, []openrtb.Imp) { + prices2 := make([]float64, 0) + imps2 := make([]openrtb.Imp, 0) + + for i := range prices { + if prices[i] > 0 { + prices2 = append(prices2, prices[i]) + imps2 = append(imps2, imps[i]) + } + } + + return prices2, imps2 +} + +// Helpers to test equality + +func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected != actual { + msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected == actual { + msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertTrue(t *testing.T, val bool, msg string) { + if val == false { + msg = fmt.Sprintf("%s: is false but should be true", msg) + t.Fatal(msg) + } +} + +func assertFalse(t *testing.T, val bool, msg string) { + if val == true { + msg = fmt.Sprintf("%s: is true but should be false", msg) + t.Fatal(msg) + } +} diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index b248e2e9dc1..64ddc38bf1a 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -1,291 +1,176 @@ package conversant import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" - "net/http" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" ) type ConversantAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Corresponds to the bidder name in cookies and requests -func (a *ConversantAdapter) Name() string { - return "conversant" + URI string } -// Return true so no request will be sent unless user has been sync'ed. -func (a *ConversantAdapter) SkipNoCookies() bool { - return true -} - -type conversantParams struct { - SiteID string `json:"site_id"` - Secure *int8 `json:"secure"` - TagID string `json:"tag_id"` - Position *int8 `json:"position"` - BidFloor float64 `json:"bidfloor"` - Mobile *int8 `json:"mobile"` - MIMEs []string `json:"mimes"` - API []int8 `json:"api"` - Protocols []int8 `json:"protocols"` - MaxDuration *int64 `json:"maxduration"` -} - -func (a *ConversantAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - // Create a map of impression objects for both request creation - // and response parsing. - - impMap := make(map[string]*openrtb.Imp, len(cnvrReq.Imp)) - for idx := range cnvrReq.Imp { - impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] - } - - // Fill in additional info from custom params - - for _, unit := range bidder.AdUnits { - var params conversantParams - - imp := impMap[unit.Code] - if imp == nil { - // Skip ad units that do not have corresponding impressions. - continue - } - - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } +func (c ConversantAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + for i := 0; i < len(request.Imp); i++ { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[i].Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] missing ext object", i), + }} } - // Fill in additional Site info - if params.SiteID != "" { - if cnvrReq.Site != nil { - cnvrReq.Site.ID = params.SiteID - } - if cnvrReq.App != nil { - cnvrReq.App.ID = params.SiteID - } + var cnvrExt openrtb_ext.ExtImpConversant + if err := json.Unmarshal(bidderExt.Bidder, &cnvrExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] missing ext.bidder object", i), + }} } - if params.Mobile != nil && !(cnvrReq.Site == nil) { - cnvrReq.Site.Mobile = *params.Mobile + if cnvrExt.SiteID == "" { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] requires ext.bidder.site_id", i), + }} } - // Fill in additional impression info - - imp.DisplayManager = "prebid-s2s" - imp.DisplayManagerVer = "1.0.1" - imp.BidFloor = params.BidFloor - imp.TagID = params.TagID - - var position *openrtb.AdPosition - if params.Position != nil { - position = openrtb.AdPosition(*params.Position).Ptr() + if i == 0 { + if request.Site != nil { + tmpSite := *request.Site + request.Site = &tmpSite + request.Site.ID = cnvrExt.SiteID + } else if request.App != nil { + tmpApp := *request.App + request.App = &tmpApp + request.App.ID = cnvrExt.SiteID + } } + parseCnvrParams(&request.Imp[i], cnvrExt) + } - if imp.Banner != nil { - imp.Banner.Pos = position - } else if imp.Video != nil { - imp.Video.Pos = position + //create the request body + data, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error in packaging request to JSON"), + }} + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: c.URI, + Body: data, + Headers: headers, + }}, nil +} - if len(params.API) > 0 { - imp.Video.API = make([]openrtb.APIFramework, 0, len(params.API)) - for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) - } - } +func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { + imp.DisplayManager = "prebid-s2s" + imp.DisplayManagerVer = "2.0.0" + imp.BidFloor = cnvrExt.BidFloor + imp.TagID = cnvrExt.TagID - // Include protocols, mimes, and max duration if specified - // These properties can also be specified in ad unit's video object, - // but are overridden if the custom params object also contains them. + // Take care not to override the global secure flag + if (imp.Secure == nil || *imp.Secure == 0) && cnvrExt.Secure != nil { + imp.Secure = cnvrExt.Secure + } - if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb.Protocol, 0, len(params.Protocols)) - for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) - } - } + var position *openrtb.AdPosition + if cnvrExt.Position != nil { + position = openrtb.AdPosition(*cnvrExt.Position).Ptr() + } + if imp.Banner != nil { + tmpBanner := *imp.Banner + imp.Banner = &tmpBanner + imp.Banner.Pos = position - if len(params.MIMEs) > 0 { - imp.Video.MIMEs = make([]string, len(params.MIMEs)) - copy(imp.Video.MIMEs, params.MIMEs) - } + } else if imp.Video != nil { + tmpVideo := *imp.Video + imp.Video = &tmpVideo + imp.Video.Pos = position - if params.MaxDuration != nil { - imp.Video.MaxDuration = *params.MaxDuration + if len(cnvrExt.API) > 0 { + imp.Video.API = make([]openrtb.APIFramework, 0, len(cnvrExt.API)) + for _, api := range cnvrExt.API { + imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) } } - // Take care not to override the global secure flag + // Include protocols, mimes, and max duration if specified + // These properties can also be specified in ad unit's video object, + // but are overridden if the custom params object also contains them. - if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { - imp.Secure = params.Secure + if len(cnvrExt.Protocols) > 0 { + imp.Video.Protocols = make([]openrtb.Protocol, 0, len(cnvrExt.Protocols)) + for _, protocol := range cnvrExt.Protocols { + imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + } } - } - - // Do a quick check on required parameters - if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing site id", + if len(cnvrExt.MIMEs) > 0 { + imp.Video.MIMEs = make([]string, len(cnvrExt.MIMEs)) + copy(imp.Video.MIMEs, cnvrExt.MIMEs) } - } - if cnvrReq.App != nil && cnvrReq.App.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing app id", + if cnvrExt.MaxDuration != nil { + imp.Video.MaxDuration = *cnvrExt.MaxDuration } } +} - // Start capturing debug info - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb.Device{} - } - - // Convert request to json to be sent over http - - j, _ := json.Marshal(cnvrReq) - - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if req.IsDebug { - debug.StatusCode = resp.StatusCode - } - - if resp.StatusCode == 204 { - return nil, nil - } - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err +func (c ConversantAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil // no bid response } - if resp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d", response.StatusCode), + }} } - if resp.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } + var resp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &resp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("bad server response: %d. ", err), + }} } - if req.IsDebug { - debug.ResponseBody = string(body) + if len(resp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Empty bid request"), + }} } - var bidResp openrtb.BidResponse - - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } + bids := resp.SeatBid[0].Bid + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bids)) + for i := 0; i < len(bids); i++ { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bids[i], + BidType: getBidType(bids[i].ImpID, internalRequest.Imp), + }) } + return bidResponse, nil +} - bids := make(pbs.PBSBidSlice, 0) - - for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { - if bid.Price <= 0 { - continue - } - - imp := impMap[bid.ImpID] - if imp == nil { - // All returned bids should have a matching impression - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), - } - } - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbsBid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - Price: bid.Price, - Creative_id: bid.CrID, - BidderCode: bidder.BidderCode, - } - +func getBidType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + bidType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { if imp.Video != nil { - pbsBid.CreativeMediaType = "video" - pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl - pbsBid.Width = imp.Video.W - pbsBid.Height = imp.Video.H - } else { - pbsBid.CreativeMediaType = "banner" - pbsBid.NURL = bid.NURL - pbsBid.Adm = bid.AdM - pbsBid.Width = bid.W - pbsBid.Height = bid.H + bidType = openrtb_ext.BidTypeVideo } - - bids = append(bids, &pbsBid) + break } } - - if len(bids) == 0 { - return nil, nil - } - - return bids, nil + return bidType } -func NewConversantAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantAdapter { - a := adapters.NewHTTPAdapter(config) - - return &ConversantAdapter{ - http: a, - URI: uri, - } +func NewConversantBidder(endpoint string) *ConversantAdapter { + return &ConversantAdapter{URI: endpoint} } diff --git a/adapters/conversant/conversant_test.go b/adapters/conversant/conversant_test.go index b958e320dc7..e3275030e83 100644 --- a/adapters/conversant/conversant_test.go +++ b/adapters/conversant/conversant_test.go @@ -1,853 +1,10 @@ package conversant import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" + "github.com/prebid/prebid-server/adapters/adapterstest" "testing" - "time" - - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" ) -// Constants - -const ExpectedSiteID string = "12345" -const ExpectedDisplayManager string = "prebid-s2s" -const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" -const ExpectedNURL string = "http://test.dotomi.com" -const ExpectedAdM string = "" -const ExpectedCrID string = "98765" - -const DefaultParam = `{"site_id": "12345"}` - -// Test properties of Adapter interface - -func TestConversantProperties(t *testing.T) { - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - assertNotEqual(t, an.Name(), "", "Missing family name") - assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") -} - -// Test empty bid requests - -func TestConversantEmptyBid(t *testing.T) { - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - assertTrue(t, err != nil, "No error received for an invalid request") -} - -// Test required parameters, which is just the site id for now - -func TestConversantRequiredParameters(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - ctx := context.TODO() - - testParams := func(params ...string) (pbs.PBSBidSlice, error) { - req, err := CreateBannerRequest(params...) - if err != nil { - return nil, err - } - return an.Call(ctx, req, req.Bidders[0]) - } - - var err error - - if _, err = testParams(`{}`); err == nil { - t.Fatal("Failed to catch missing site id") - } -} - -// Test handling of 404 - -func TestConversantBadStatus(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assertTrue(t, err != nil, "Failed to catch 404 error") -} - -// Test handling of HTTP timeout - -func TestConversantTimeout(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - // Create a context that expires before http returns - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - // Create a basic request - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - // Attempt to process the request, which should hit a timeout - // immediately - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil || err != context.DeadlineExceeded { - t.Fatal("No timeout recevied for timed out request", err) - } -} - -// Test handling of 204 - -func TestConversantNoBid(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if resp != nil || err != nil { - t.Fatal("Failed to handle empty bid", err) - } -} - -// Verify an outgoing openrtp request is created correctly - -func TestConversantRequestDefault(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, imp.TagID, "", "Request tag id") - assertTrue(t, imp.Banner.Pos == nil, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify inapp video request -func TestConversantInappVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - requestParam := `{"secure": 1, "site_id": "12345"}` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(requestParam) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - assertEqual(t, lastReq.App.ID, "12345", "App Id") -} - -// Verify inapp video request -func TestConversantInappBannerRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "secure": 1, - "site_id": "12345", - "tag_id": "top", - "position": 2, - "bidfloor": 1.01 }` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - - ctx := context.TODO() - pbReq, _ := CreateBannerRequest(param) - pbReq, err := ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, lastReq.App.ID, "12345", "App Id") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify an outgoing openrtp request with additional conversant parameters is -// processed correctly - -func TestConversantRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile": 1 }` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 1, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "top", "Request tag id") - assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify openrtp responses are converted correctly - -func TestConversantResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") - assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") - } - } -} - -// Test video request - -func TestConversantBasicVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") - assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") - assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") - assertTrue(t, imp.Video.API == nil, "Request video api should be nil") -} - -// Test video request with parameters in custom params object - -func TestConversantVideoRequestWithParams(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01, - "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "api": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") - assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb.APIFramework(2), "Request video api 2") -} - -// Test video request with parameters in the video object - -func TestConversantVideoRequestWithParams2(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(param) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("Failed to convert to a video request", err) - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("Failed to parse video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") -} - -// Test video responses - -func TestConversantVideoResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, "", "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") - assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") - } - } -} - -// Helpers to create a banner and video requests - -func CreateRequest(params ...string) *pbs.PBSRequest { - num := len(params) - - req := pbs.PBSRequest{ - Tid: "t-000", - AccountID: "1", - AdUnits: make([]pbs.AdUnit, num), - } - - for i := 0; i < num; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb.Format{ - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "conversant", - BidID: fmt.Sprintf("b-%03d", i), - Params: json.RawMessage(params[i]), - }, - }, - } - } - - return &req -} - -// Convert a request to a video request by adding required properties - -func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { - for i := 0; i < len(req.AdUnits); i++ { - video := pbs.PBSVideo{} - if i < len(videoParams) { - err := json.Unmarshal([]byte(videoParams[i]), &video) - if err != nil { - return nil, err - } - } - - if video.Mimes == nil { - video.Mimes = []string{"video/mp4"} - } - - req.AdUnits[i].Video = video - req.AdUnits[i].MediaTypes = []string{"video"} - } - - return req, nil -} - -// Convert a request to an app request by adding required properties -func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb.App) - err := json.Unmarshal([]byte(appParams), &app) - if err == nil { - req.App = app - } - - return req, nil -} - -// Feed the request thru the prebid parser so user id and -// other private properties are defined - -func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { - body := new(bytes.Buffer) - _ = json.NewEncoder(body).Encode(req) - - // Need to pass the conversant user id thru uid cookie - - httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewPBSCookie() - _ = cookie.TrySync("conversant", ExpectedBuyerUID) - httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) - httpReq.Header.Add("Referer", "http://example.com") - cache, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cache, &hcc) - - return parsedReq, err -} - -// A helper to create a banner request - -func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ParseRequest(req) - return req, err -} - -// A helper to create a video request - -func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ConvertToVideoRequest(req) - if err != nil { - return nil, err - } - req, err = ParseRequest(req) - return req, err -} - -// Helper to create a test http server that receives and generate openrtb requests and responses - -func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { - var lastBidRequest openrtb.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var bidReq openrtb.BidRequest - var price float64 - var bids []openrtb.Bid - var bid openrtb.Bid - - err = json.Unmarshal(body, &bidReq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - lastBidRequest = bidReq - - for i, imp := range bidReq.Imp { - if i < len(prices) { - price = prices[i] - } else { - price = 0 - } - - if price > 0 { - bid = openrtb.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: price, - NURL: ExpectedNURL, - AdM: ExpectedAdM, - CrID: ExpectedCrID, - } - - if imp.Banner != nil { - bid.W = *imp.Banner.W - bid.H = *imp.Banner.H - } else if imp.Video != nil { - bid.W = imp.Video.W - bid.H = imp.Video.H - } - } else { - bid = openrtb.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: 0, - } - } - - bids = append(bids, bid) - } - - if len(bids) == 0 { - w.WriteHeader(http.StatusNoContent) - } else { - js, _ := json.Marshal(openrtb.BidResponse{ - ID: bidReq.ID, - SeatBid: []openrtb.SeatBid{ - { - Bid: bids, - }, - }, - }) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) - } - }), - ) - - return server, &lastBidRequest -} - -// Helper to remove impressions with $0 bids - -func FilterZeroPrices(prices []float64, imps []openrtb.Imp) ([]float64, []openrtb.Imp) { - prices2 := make([]float64, 0) - imps2 := make([]openrtb.Imp, 0) - - for i := range prices { - if prices[i] > 0 { - prices2 = append(prices2, prices[i]) - imps2 = append(imps2, imps[i]) - } - } - - return prices2, imps2 -} - -// Helpers to test equality - -func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected != actual { - msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected == actual { - msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertTrue(t *testing.T, val bool, msg string) { - if val == false { - msg = fmt.Sprintf("%s: is false but should be true", msg) - t.Fatal(msg) - } -} - -func assertFalse(t *testing.T, val bool, msg string) { - if val == true { - msg = fmt.Sprintf("%s: is true but should be false", msg) - t.Fatal(msg) - } +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "conversanttest", NewConversantBidder("")) } diff --git a/adapters/conversant/conversanttest/exemplary/banner.json b/adapters/conversant/conversanttest/exemplary/banner.json new file mode 100644 index 00000000000..472e18f712d --- /dev/null +++ b/adapters/conversant/conversanttest/exemplary/banner.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "testauction", + "imp": [ + { + "id": "1", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "site_id": "108060", + "bidfloor": 0.01, + "tag_id": "mytag", + "secure": 1 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "192.168.1.1", + "dnt": 1 + }, + "site": { + "domain": "www.mypage.com" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "", + "body": { + "id": "testauction", + "site": { + "id": "108060", + "domain": "www.mypage.com" + }, + "imp": [ + { + "id": "1", + "tagid": "mytag", + "secure": 1, + "bidfloor": 0.01, + "displaymanager": "prebid-s2s", + "displaymanagerver": "2.0.0", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "site_id": "108060", + "bidfloor": 0.01, + "tag_id": "mytag", + "secure": 1 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "192.168.1.1", + "dnt": 1 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "testauction", + "bidid": "c8d95f4b-bcbb-4a6c-adbb-4c7f33af3c24", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "1", + "price": 0.0340, + "nurl": "https:\/\/event.ad.cpe.dotomi.com\/cvx\/event\/imp?enc=eyJ1c2VyaWQiOiI3MTI3MDUzNzM3NTM3MTAzMjIiLCJwYXJ0bmVyVHhpZCI6ImUyZWUzNjZlLWEyMjgtNDI0Mi1hNjJlLTk4ODk3ODhiYzgxNCIsInR4aWQiOiI3MTE1NzQwNDg3NTczODUwMDIiLCJuZXR3b3JrUmVxdWVzdElkIjoiNzExNTc0MDQ4NzU3Mzg1ODc0Iiwic2lkIjoxMTgwOTgsImRpdmlzaW9uSWQiOjgsInRpZCI6OCwibW9iaWxlRGF0YSI6IjU5IiwiYmlkUHJpY2UiOjAuMDY4MCwicHViQ29zdCI6MC4wMzQwLCJwYXJ0bmVyRmVlIjowLjAxMzYsImlwU3RyaW5nIjoiNzMuMTE4LjEzMC4xODYiLCJzdXBwbHlUeXBlIjoxLCJpbnRlZ3JhdGlvblR5cGUiOjQsIm1lZGlhdGlvblR5cGUiOjEyNiwicGxhY2VtZW50SWQiOiIxMTY5ODcwIiwiaGVhZGVyQmlkIjoxLCJpc0RpcmVjdFB1Ymxpc2hlciI6MCwiaGFzQ29uc2VudCI6MSwib3BlcmF0aW9uIjoiQ0xJRU5UX0hFQURFUl8yNSIsImlzQ29yZVNoaWVsZCI6MCwicGFydG5lckNyZWF0aXZlSWQiOiIyNDk2NDRfMzAweDI1MCIsInBhcnRuZXJEb21haW5zIjpbIndhbG1hcnQuY29tIl0sInNlbGxlclJlcXVlc3RJZCI6ImE3ODcyMWQ3LWE2ZmUtNGJiNS1hNjFkLTFhMDg1MzkxZTVlZCIsInNlbGxlckltcElkIjoiMzAwNDIxZDY0NWY2ZjRjOWMifQ&", + "adm": "", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "ttl": 300, + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBids": [ + { + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "ttl": 300, + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + }, + "type": "video" + } + ] +} + \ No newline at end of file diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json new file mode 100644 index 00000000000..c2b20cf1c5d --- /dev/null +++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json @@ -0,0 +1,200 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + } + }, + { + "id": "some_test_ad_id_2", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Referer": [ + "http://www.publisher.com/awesome/site?with=some¶meters=here" + ], + "Dnt": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + }, + "tagid": "25251", + "secure": 0 + }, + { + "id": "some_test_ad_id_2", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + }, + "tagid": "25251", + "secure": 0 + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + }, + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "ttl": 300, + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "ttl": 300, + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + }, + "type": "video" + } + ] +} + \ No newline at end of file diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json new file mode 100644 index 00000000000..8de90f52192 --- /dev/null +++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + }, + "app": { + "domain": "www.publisher.com", + "storeurl": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.emxdgt.com?t=1000&ts=2060541160", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ], + "Dnt": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tagid": "25251" + } + }, + "tagid": "25251", + "secure": 0 + }], + "app": { + "domain": "www.publisher.com", + "storeurl": "http://www.publisher.com/awesome/site?with=some¶meters=here" + }, + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "dnt": 1 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "h": 300, + "w": 250 + }] + }], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "TEST", + "impid": "1", + "price": 10.0, + "adid": "1", + "adm": "", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "h": 300, + "w": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/amx/amxtest/exemplary/video-simple.json b/adapters/amx/amxtest/exemplary/video-simple.json new file mode 100644 index 00000000000..8fb3baa26d0 --- /dev/null +++ b/adapters/amx/amxtest/exemplary/video-simple.json @@ -0,0 +1,245 @@ +{ + "mockBidRequest": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "boxingallowed": 1, + "linearity": 1, + "maxduration": 90, + "minduration": 6, + "mimes": ["video/mp4"], + "placement": 1, + "playbackmethod": [2], + "protocols": [1,2,3,4,5,6,7,8], + "skip": 1, + "skipafter": 5, + "startdelay": 0, + "h": 300, + "pos": 1, + "w": 640 + }, + "ext": { + "bidder": { + "tagId": "cHJlYmlkLm9yZw", + "adUnitId": "tagid-override" + } + }, + "id": "1", + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "1---" + } + }, + "site": { + "domain": "www.example.com", + "ext": { + "amp": 0 + }, + "publisher": { + "id": "unused_publisher_id" + }, + "page": "https://www.example.com/es6/es6_objects.htm", + "ref": "https://www.example.com/es6/es6_objects.htm" + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "test": 0, + "tmax": 300, + "user": { + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + } + ], + "gdpr": 0, + "us_privacy": "1---" + } + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "body": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "boxingallowed": 1, + "linearity": 1, + "maxduration": 90, + "minduration": 6, + "mimes": ["video/mp4"], + "placement": 1, + "playbackmethod": [2], + "protocols": [1,2,3,4,5,6,7,8], + "skip": 1, + "skipafter": 5, + "startdelay": 0, + "h": 300, + "pos": 1, + "w": 640 + }, + "ext": { + "bidder": { + "tagId": "cHJlYmlkLm9yZw", + "adUnitId": "tagid-override" + } + }, + "tagid": "tagid-override", + "id": "1", + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "1---" + } + }, + "site": { + "domain": "www.example.com", + "ext": { + "amp": 0 + }, + "publisher": { + "id": "cHJlYmlkLm9yZw" + }, + "page": "https://www.example.com/es6/es6_objects.htm", + "ref": "https://www.example.com/es6/es6_objects.htm" + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "tmax": 300, + "user": { + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + } + ], + "gdpr": 0, + "us_privacy": "1---" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "WQ5V2DWVTMNXABDD", + "seatbid": [{ + "bid": [{ + "id": "TEST", + "impid": "1", + "price": 10.0, + "adid": "1", + "adm": "00:00:15", + "nurl": "https://example.com/nurl", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "h": 600, + "w": 300, + "ext": { + "himp": ["https://example.com/imp-tracker/pixel.gif?param=1¶m2=2"], + "startdelay": 0 + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "TEST", + "impid": "1", + "price": 10.0, + "adid": "1", + "adm": "00:00:15", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "ext": { + "himp": ["https://example.com/imp-tracker/pixel.gif?param=1¶m2=2"], + "startdelay": 0 + }, + "h": 600, + "w": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/amx/amxtest/exemplary/web-simple.json b/adapters/amx/amxtest/exemplary/web-simple.json new file mode 100644 index 00000000000..74854f912ae --- /dev/null +++ b/adapters/amx/amxtest/exemplary/web-simple.json @@ -0,0 +1,246 @@ +{ + "mockBidRequest": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "banner": { + "format": [ + { + "h": 600, + "w": 300 + } + ], + "h": 600, + "pos": 1, + "w": 300 + }, + "ext": { + "bidder": { + "tagId": "cHJlYmlkLm9yZw" + } + }, + "tagid": "example-tag-id", + "id": "1", + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "1---" + } + }, + "site": { + "domain": "www.example.com", + "ext": { + "amp": 0 + }, + "publisher": { + "id": "unused_publisher_id" + }, + "page": "https://www.example.com/es6/es6_objects.htm", + "ref": "https://www.example.com/es6/es6_objects.htm" + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "test": 0, + "tmax": 300, + "user": { + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + }, + { + "source": "adserver.org", + "uids": [ + { + "id": "1234567", + "ext": { + "rtiPartner": "TDID" + } + } + ] + } + ], + "gdpr": 0, + "us_privacy": "1---" + } + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "body": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "tagid": "example-tag-id", + "banner": { + "format": [ + { + "h": 600, + "w": 300 + } + ], + "h": 600, + "pos": 1, + "w": 300 + }, + "ext": { + "bidder": { + "tagId": "cHJlYmlkLm9yZw" + } + }, + "id": "1", + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 0, + "us_privacy": "1---" + } + }, + "site": { + "domain": "www.example.com", + "ext": { + "amp": 0 + }, + "publisher": { + "id": "cHJlYmlkLm9yZw" + }, + "page": "https://www.example.com/es6/es6_objects.htm", + "ref": "https://www.example.com/es6/es6_objects.htm" + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "tmax": 300, + "user": { + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + }, + { + "source": "adserver.org", + "uids": [ + { + "id": "1234567", + "ext": { + "rtiPartner": "TDID" + } + } + ] + } + ], + "gdpr": 0, + "us_privacy": "1---" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "WQ5V2DWVTMNXABDD", + "seatbid": [{ + "bid": [{ + "id": "TEST", + "impid": "1", + "price": 10.0, + "adid": "1", + "adm": "", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "h": 600, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "TEST", + "impid": "1", + "price": 10.0, + "adid": "1", + "adm": "", + "adomain": ["amxrtb.com"], + "iurl": "https://assets.a-mo.net/300x250.v2.png", + "cid": "1", + "crid": "1", + "h": 600, + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/amx/amxtest/params/race/display.json b/adapters/amx/amxtest/params/race/display.json new file mode 100644 index 00000000000..bd101e95a25 --- /dev/null +++ b/adapters/amx/amxtest/params/race/display.json @@ -0,0 +1 @@ +{"tagId":"sample345", "adUnitId": "sampleAdUnitID"} \ No newline at end of file diff --git a/adapters/amx/amxtest/params/race/video.json b/adapters/amx/amxtest/params/race/video.json new file mode 100644 index 00000000000..d2f11bf80b4 --- /dev/null +++ b/adapters/amx/amxtest/params/race/video.json @@ -0,0 +1 @@ +{"tagId": "sample123", "adUnitId": "sampleAdUnitID"} \ No newline at end of file diff --git a/adapters/amx/amxtest/supplemental/204-response.json b/adapters/amx/amxtest/supplemental/204-response.json new file mode 100644 index 00000000000..09571a03569 --- /dev/null +++ b/adapters/amx/amxtest/supplemental/204-response.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "test": 0, + "tmax": 300 + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "body": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "mimes": null, + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "tmax": 300 + } + }, + "mockResponse": { + "status": 204, + "headers": { + "X-Nbr": [ + "3b" + ] + }, + "body": {} + } + }], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/amx/amxtest/supplemental/400-response.json b/adapters/amx/amxtest/supplemental/400-response.json new file mode 100644 index 00000000000..f10cea89718 --- /dev/null +++ b/adapters/amx/amxtest/supplemental/400-response.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "test": 0, + "tmax": 300 + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "body": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "mimes": null, + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "tmax": 300 + } + }, + "mockResponse": { + "status": 400, + "headers": { + "X-Nbr": [ + "3b" + ] + }, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Invalid Request: 400. Error Code: 3b", + "comparison": "literal" + } + ] +} diff --git a/adapters/amx/amxtest/supplemental/500-response.json b/adapters/amx/amxtest/supplemental/500-response.json new file mode 100644 index 00000000000..fe5d89930c8 --- /dev/null +++ b/adapters/amx/amxtest/supplemental/500-response.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "test": 0, + "tmax": 300 + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "body": { + "device": { + "dnt": 0, + "h": 1120, + "ip": "98.249.0.0", + "language": "en", + "os": "macos", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", + "w": 1792 + }, + "id": "TL3JS6F43CKNDQFY", + "imp": [ + { + "video": { + "api": [1,2], + "mimes": null, + "h": 300, + "pos": 1, + "w": 640 + }, + "id": "1", + "secure": 1 + } + ], + "site": { + "ext": { + "amp": 0 + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "amxrtb.com", + "hp": 1, + "sid": "1234" + } + ], + "ver": "1.0" + } + } + }, + "tmax": 300 + } + }, + "mockResponse": { + "status": 500, + "headers": { + "X-Nbr": [ + "7a" + ] + }, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected response: 500. Error Code: 7a", + "comparison": "literal" + } + ] +} diff --git a/adapters/amx/params_test.go b/adapters/amx/params_test.go new file mode 100644 index 00000000000..89e9a3adeb4 --- /dev/null +++ b/adapters/amx/params_test.go @@ -0,0 +1,47 @@ +package amx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +var validBidParams = []string{ + `{"tagId":"sampleTagId", "adUnitId": "sampleAdUnitId"}`, + `{"tagId":"sampleTagId", "adUnitId": ""}`, + `{"adUnitId": ""}`, + `{"adUnitId": "sampleAdUnitId"}`, + `{"tagId":"sampleTagId"}`, + `{"tagId":""}`, + `{}`, + `{"otherValue": "ignored"}`, + `{"tagId": "sampleTagId", "otherValue": "ignored"}`, + `{"otherValue": "ignored", "adUnitId": "sampleAdUnitId"}`, +} + +var invalidBidParams = []string{ + `{"tagId":1234}`, + `{"tagId": true}`, + `{"adUnitId": true}`, + `{"adUnitId": null}`, + `{"adUnitId": null, "tagId": "sampleTagId"}`, + `{"adUnitId": 1234, "tagId": "sampleTagId"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + assert.Nil(t, err) + for _, params := range validBidParams { + assert.Nil(t, validator.Validate(openrtb_ext.BidderAMX, json.RawMessage(params))) + } +} + +func TestInValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + assert.Nil(t, err) + for _, params := range invalidBidParams { + assert.NotNil(t, validator.Validate(openrtb_ext.BidderAMX, json.RawMessage(params))) + } +} diff --git a/adapters/amx/usersync.go b/adapters/amx/usersync.go new file mode 100644 index 00000000000..28e6ac0ed79 --- /dev/null +++ b/adapters/amx/usersync.go @@ -0,0 +1,13 @@ +package amx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +// NewAMXSyncer produces an AMX RTB usersyncer +func NewAMXSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("amx", 737, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/amx/usersync_test.go b/adapters/amx/usersync_test.go new file mode 100644 index 00000000000..20a47c33b69 --- /dev/null +++ b/adapters/amx/usersync_test.go @@ -0,0 +1,23 @@ +package amx + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestAMXSyncer(t *testing.T) { + syncURL := "http://pbs.amxrtb.com/cchain/0?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D" + syncURLTemplate := template.Must(template.New("sync-template").Parse(syncURL)) + + syncer := NewAMXSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "http://pbs.amxrtb.com/cchain/0?gdpr=&gdpr_consent=&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 737, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index de33bd390e5..dfb97f33abb 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -4,13 +4,14 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "net/url" + "strings" + "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "net/http" - "net/url" - "strings" ) type DmxAdapter struct { diff --git a/config/config.go b/config/config.go index 6d8fdbc070d..c2db7b7d03c 100755 --- a/config/config.go +++ b/config/config.go @@ -712,6 +712,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAMX, "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Damx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") @@ -939,6 +940,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") + v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs v.SetDefault("adapters.appnexus.platform_id", "5") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 30d8d45641f..ff72165bcb3 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -25,6 +25,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/aja" + "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/applogy" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" @@ -122,6 +123,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderAdtelligent: adtelligent.NewAdtelligentBidder(cfg.Adapters[string(openrtb_ext.BidderAdtelligent)].Endpoint), openrtb_ext.BidderAdvangelists: advangelists.NewAdvangelistsBidder(cfg.Adapters[string(openrtb_ext.BidderAdvangelists)].Endpoint), openrtb_ext.BidderAJA: aja.NewAJABidder(cfg.Adapters[string(openrtb_ext.BidderAJA)].Endpoint), + openrtb_ext.BidderAMX: amx.NewAMXBidder(cfg.Adapters[string(openrtb_ext.BidderAMX)].Endpoint), openrtb_ext.BidderApplogy: applogy.NewApplogyBidder(cfg.Adapters[string(openrtb_ext.BidderApplogy)].Endpoint), openrtb_ext.BidderAppnexus: appnexus.NewAppNexusBidder(client, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), openrtb_ext.BidderAvocet: avocet.NewAvocetAdapter(cfg.Adapters[string(openrtb_ext.BidderAvocet)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index dd482a2ea44..b98c80ae9af 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -43,6 +43,7 @@ const ( BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderAJA BidderName = "aja" + BidderAMX BidderName = "amx" BidderApplogy BidderName = "applogy" BidderAppnexus BidderName = "appnexus" BidderAdoppler BidderName = "adoppler" @@ -136,6 +137,7 @@ var BidderMap = map[string]BidderName{ "adtelligent": BidderAdtelligent, "advangelists": BidderAdvangelists, "aja": BidderAJA, + "amx": BidderAMX, "applogy": BidderApplogy, "appnexus": BidderAppnexus, "adoppler": BidderAdoppler, diff --git a/openrtb_ext/imp_amx.go b/openrtb_ext/imp_amx.go new file mode 100644 index 00000000000..d4439d05f60 --- /dev/null +++ b/openrtb_ext/imp_amx.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpAMX is the imp.ext format for the AMX bidder +type ExtImpAMX struct { + TagID string `json:"tagId,omitempty"` + AdUnitID string `json:"adUnitId,omitempty"` +} diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml new file mode 100644 index 00000000000..3e20d2095f6 --- /dev/null +++ b/static/bidder-info/amx.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "prebid@amxrtb.com" +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/amx.json b/static/bidder-params/amx.json new file mode 100644 index 00000000000..f9b1b26b3db --- /dev/null +++ b/static/bidder-params/amx.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AMX RTB Adapter Params", + "description": "A schema to validate params accepted by the AMX adapter", + "type": "object", + "properties": { + "tagId" : { + "type": "string", + "description": "Set a tagId (overrides site.publisher.id, or app.publisher.id)" + }, + "adUnitId": { + "type": "string", + "description": "Override imp.tagid value to provide a custom value in AMX ad unit ID reporting" + } + } +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index a9d909db9a1..d6b3092a56d 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" "github.com/prebid/prebid-server/adapters/aja" + "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" "github.com/prebid/prebid-server/adapters/avocet" @@ -102,6 +103,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAMX, amx.NewAMXSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAppnexus, appnexus.NewAppnexusSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAvocet, avocet.NewAvocetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 8ae8581aa6e..7582055fa46 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -27,6 +27,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, + string(openrtb_ext.BidderAMX): syncConfig, string(openrtb_ext.BidderAppnexus): syncConfig, string(openrtb_ext.BidderAvocet): syncConfig, string(openrtb_ext.BidderBeachfront): syncConfig, From 63f5bcfb9f310f62299ecaac5ef053dee31041fc Mon Sep 17 00:00:00 2001 From: htang555 Date: Tue, 10 Nov 2020 13:37:04 -0500 Subject: [PATCH 246/603] update Datablocks usersync.go (#1572) --- adapters/datablocks/usersync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/datablocks/usersync.go b/adapters/datablocks/usersync.go index c8ec92aa857..2b47b259e39 100644 --- a/adapters/datablocks/usersync.go +++ b/adapters/datablocks/usersync.go @@ -7,8 +7,8 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const datablocksGDPRVendorID = uint16(14) +const datablocksGDPRVendorID = uint16(0) func NewDatablocksSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("datablocks", 14, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("datablocks", datablocksGDPRVendorID, temp, adapters.SyncTypeRedirect) } From 6d37afcdc109a4671644a18a66181c1bd75e6568 Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Wed, 11 Nov 2020 03:59:50 -0500 Subject: [PATCH 247/603] 33Across: Add video support in adapter (#1557) --- adapters/33across/33across.go | 77 +++++++++++- adapters/33across/33across_test.go | 2 +- .../exemplary/bidresponse-defaults.json | 100 ++++++++++++++++ .../exemplary/instream-video-defaults.json | 108 +++++++++++++++++ .../33acrosstest/exemplary/multi-format.json | 103 ++++++++++++++++ .../exemplary/optional-params.json | 0 .../exemplary/outstream-video-defaults.json | 107 +++++++++++++++++ .../exemplary/simple-banner.json | 14 ++- .../33acrosstest/exemplary/simple-video.json | 110 ++++++++++++++++++ .../params/race/banner.json | 0 .../33acrosstest/params/race/video.json | 6 + .../supplemental/status-not-ok.json | 67 +++++++++++ .../supplemental/video-validation-fail.json | 32 +++++ static/bidder-info/33across.yaml | 2 + 14 files changed, 724 insertions(+), 4 deletions(-) create mode 100644 adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json create mode 100644 adapters/33across/33acrosstest/exemplary/instream-video-defaults.json create mode 100644 adapters/33across/33acrosstest/exemplary/multi-format.json rename adapters/33across/{33across => 33acrosstest}/exemplary/optional-params.json (100%) create mode 100644 adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json rename adapters/33across/{33across => 33acrosstest}/exemplary/simple-banner.json (85%) create mode 100644 adapters/33across/33acrosstest/exemplary/simple-video.json rename adapters/33across/{33across => 33acrosstest}/params/race/banner.json (100%) create mode 100644 adapters/33across/33acrosstest/params/race/video.json create mode 100644 adapters/33across/33acrosstest/supplemental/status-not-ok.json create mode 100644 adapters/33across/33acrosstest/supplemental/video-validation-fail.json diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index 5c1b31eeb8c..40099a204e0 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -24,6 +24,14 @@ type ext struct { Zoneid string `json:"zoneid,omitempty"` } +type bidExt struct { + Ttx bidTtxExt `json:"ttx,omitempty"` +} + +type bidTtxExt struct { + MediaType string `json:mediaType,omitempty` +} + // MakeRequests create the object for TTX Reqeust. func (a *TtxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -49,6 +57,14 @@ func (a *TtxAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Request errs = append(errs, err) } + if reqCopy.Imp[0].Banner == nil && reqCopy.Imp[0].Video == nil { + errs = append(errs, &errortypes.BadInput{ + Message: "At least one of [banner, video] formats must be defined in Imp. None found", + }) + + return nil, errs + } + // Last Step reqJSON, err := json.Marshal(reqCopy) if err != nil { @@ -104,6 +120,19 @@ func preprocess(request *openrtb.BidRequest) error { siteCopy.ID = ttxExt.SiteId request.Site = &siteCopy + // Validate Video if it exists + if imp.Video != nil { + videoCopy, err := validateVideoParams(imp.Video, impExt.Ttx.Prod) + + imp.Video = videoCopy + + if err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + } + return nil } @@ -135,9 +164,18 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { + bidType = openrtb_ext.BidTypeBanner + } else { + bidType = getBidType(bidExt) + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: "banner", + BidType: bidType, }) } } @@ -145,6 +183,43 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } +func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { + videoCopy := video + if videoCopy.W == 0 || + videoCopy.H == 0 || + videoCopy.Protocols == nil || + videoCopy.MIMEs == nil || + videoCopy.PlaybackMethod == nil { + + return nil, &errortypes.BadInput{ + Message: "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", + } + } + + if videoCopy.Placement == 0 { + videoCopy.Placement = 2 + } + + if prod == "instream" { + videoCopy.Placement = 1 + + if videoCopy.StartDelay == nil { + videoCopy.StartDelay = openrtb.StartDelay.Ptr(0) + } + } + + return videoCopy, nil +} + +func getBidType(ext bidExt) openrtb_ext.BidType { + if ext.Ttx.MediaType == "video" { + return openrtb_ext.BidTypeVideo + } + + return openrtb_ext.BidTypeBanner +} + +// New33AcrossBidder configures bidder endpoint func New33AcrossBidder(endpoint string) *TtxAdapter { return &TtxAdapter{ endpoint: endpoint, diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index ccdcbf9cc2c..9856f8f9f6a 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -7,5 +7,5 @@ import ( ) func TestJsonSamples(t *testing.T) { - adapterstest.RunJSONBidderTest(t, "33across", New33AcrossBidder("http://ssc.33across.com")) + adapterstest.RunJSONBidderTest(t, "33acrosstest", New33AcrossBidder("http://ssc.33across.com")) } diff --git a/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json b/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json new file mode 100644 index 00000000000..bb0e6585fd0 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "instream" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "instream" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json b/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json new file mode 100644 index 00000000000..479b197077a --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "instream" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": 0, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "instream" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/33across/33acrosstest/exemplary/multi-format.json b/adapters/33across/33acrosstest/exemplary/multi-format.json new file mode 100644 index 00000000000..db15955ca87 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/multi-format.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "inview" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "inview" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/33across/33across/exemplary/optional-params.json b/adapters/33across/33acrosstest/exemplary/optional-params.json similarity index 100% rename from adapters/33across/33across/exemplary/optional-params.json rename to adapters/33across/33acrosstest/exemplary/optional-params.json diff --git a/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json b/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json new file mode 100644 index 00000000000..c0c31168684 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "siab" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/33across/33across/exemplary/simple-banner.json b/adapters/33across/33acrosstest/exemplary/simple-banner.json similarity index 85% rename from adapters/33across/33across/exemplary/simple-banner.json rename to adapters/33across/33acrosstest/exemplary/simple-banner.json index 074badade07..d8c215c06ae 100644 --- a/adapters/33across/33across/exemplary/simple-banner.json +++ b/adapters/33across/33acrosstest/exemplary/simple-banner.json @@ -56,7 +56,12 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 90, - "w": 728 + "w": 728, + "ext": { + "ttx": { + "mediaType": "banner" + } + } }] } ], @@ -78,7 +83,12 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 728, - "h": 90 + "h": 90, + "ext": { + "ttx": { + "mediaType": "banner" + } + } }, "type": "banner" } diff --git a/adapters/33across/33acrosstest/exemplary/simple-video.json b/adapters/33across/33acrosstest/exemplary/simple-video.json new file mode 100644 index 00000000000..55337b92827 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/simple-video.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "instream" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "instream" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/33across/33across/params/race/banner.json b/adapters/33across/33acrosstest/params/race/banner.json similarity index 100% rename from adapters/33across/33across/params/race/banner.json rename to adapters/33across/33acrosstest/params/race/banner.json diff --git a/adapters/33across/33acrosstest/params/race/video.json b/adapters/33across/33acrosstest/params/race/video.json new file mode 100644 index 00000000000..9df849ad94b --- /dev/null +++ b/adapters/33across/33acrosstest/params/race/video.json @@ -0,0 +1,6 @@ +{ + "productId": "siab", + "siteId": "33across", + "zoneId": "33AcrossZone" + } + \ No newline at end of file diff --git a/adapters/33across/33acrosstest/supplemental/status-not-ok.json b/adapters/33across/33acrosstest/supplemental/status-not-ok.json new file mode 100644 index 00000000000..98fe01c2e50 --- /dev/null +++ b/adapters/33across/33acrosstest/supplemental/status-not-ok.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": "fake-invalid-site-id", + "productId": "inview" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "ttx": { + "prod": "inview" + } + } + } + ], + "site": { + "id": "fake-invalid-site-id" + } + } + }, + "mockResponse": { + "status": 400, + "body": { + "error": { + "message": "Validation failed", + "details": [ + { + "message": "site.id is invalid" + } + ] + } + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/33across/33acrosstest/supplemental/video-validation-fail.json b/adapters/33across/33acrosstest/supplemental/video-validation-fail.json new file mode 100644 index 00000000000..97cb79bd26c --- /dev/null +++ b/adapters/33across/33acrosstest/supplemental/video-validation-fail.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90 + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } + } + ], + "site": {} + }, + + "expectedMakeRequestsErrors": [ + { + "value": "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", + "comparison": "literal" + }, + { + "value": "At least one of [banner, video] formats must be defined in Imp. None found", + "comparison": "literal" + } + ] +} diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 84ba6d68611..67e6996accf 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -4,6 +4,8 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner + - video From c481f56a9f3ca5b405a3d623e107f7389c34dc18 Mon Sep 17 00:00:00 2001 From: silvermob <73727464+silvermob@users.noreply.github.com> Date: Wed, 11 Nov 2020 21:25:30 +0700 Subject: [PATCH 248/603] SilverMob adapter (#1561) * SilverMob adapter * Fixes andchanges according to notes in PR * Remaining fixes: multibids, expectedMakeRequestsErrors * removed log * removed log * Multi-bid test * Removed unnesesary block Co-authored-by: Anton Nikityuk --- adapters/silvermob/params_test.go | 56 ++++ adapters/silvermob/silvermob.go | 188 +++++++++++ adapters/silvermob/silvermob_test.go | 11 + .../silvermobtest/exemplary/banner-app.json | 163 ++++++++++ .../exemplary/banner-multi-app.json | 293 ++++++++++++++++++ .../silvermobtest/exemplary/native-app.json | 159 ++++++++++ .../silvermobtest/exemplary/video-app.json | 171 ++++++++++ .../silvermobtest/params/race/banner.json | 4 + .../silvermobtest/params/race/native.json | 4 + .../silvermobtest/params/race/video.json | 4 + .../supplemental/empty-seatbid-array.json | 139 +++++++++ .../supplemental/invalid-response.json | 118 +++++++ .../invalid-silvermob-ext-object.json | 28 ++ .../supplemental/status-code-bad-request.json | 99 ++++++ .../supplemental/status-code-no-content.json | 83 +++++ .../supplemental/status-code-other-error.json | 87 ++++++ .../status-code-service-unavailable.json | 87 ++++++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_silvermob.go | 7 + static/bidder-info/silvermob.yaml | 8 + static/bidder-params/silvermob.json | 17 + usersync/usersyncers/syncer_test.go | 1 + 24 files changed, 1732 insertions(+) create mode 100644 adapters/silvermob/params_test.go create mode 100644 adapters/silvermob/silvermob.go create mode 100644 adapters/silvermob/silvermob_test.go create mode 100644 adapters/silvermob/silvermobtest/exemplary/banner-app.json create mode 100644 adapters/silvermob/silvermobtest/exemplary/banner-multi-app.json create mode 100644 adapters/silvermob/silvermobtest/exemplary/native-app.json create mode 100644 adapters/silvermob/silvermobtest/exemplary/video-app.json create mode 100644 adapters/silvermob/silvermobtest/params/race/banner.json create mode 100644 adapters/silvermob/silvermobtest/params/race/native.json create mode 100644 adapters/silvermob/silvermobtest/params/race/video.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/empty-seatbid-array.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/invalid-response.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/invalid-silvermob-ext-object.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/status-code-bad-request.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/status-code-no-content.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/status-code-other-error.json create mode 100644 adapters/silvermob/silvermobtest/supplemental/status-code-service-unavailable.json create mode 100644 openrtb_ext/imp_silvermob.go create mode 100644 static/bidder-info/silvermob.yaml create mode 100644 static/bidder-params/silvermob.json diff --git a/adapters/silvermob/params_test.go b/adapters/silvermob/params_test.go new file mode 100644 index 00000000000..13009f6a08b --- /dev/null +++ b/adapters/silvermob/params_test.go @@ -0,0 +1,56 @@ +package silvermob + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// TestValidParams makes sure that the silvermob schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSilverMob, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected silvermob params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the silvermob schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSilverMob, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneid": "16", "host": "us"}`, + `{"zoneid": "16", "host": "eu"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"ZoneID": "asd", "Host": "123"}`, + `{}`, + `{"ZoneID": "asd"}`, + `{"Host": "111"}`, + `{"zoneid": 16, "host": 111}`, +} diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go new file mode 100644 index 00000000000..be7a1762d23 --- /dev/null +++ b/adapters/silvermob/silvermob.go @@ -0,0 +1,188 @@ +package silvermob + +import ( + "encoding/json" + "fmt" + "github.com/golang/glog" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type SilverMobAdapter struct { + endpoint template.Template +} + +func NewSilverMobBidder(endpointTemplate string) *SilverMobAdapter { + template, err := template.New("endpointTemplate").Parse(endpointTemplate) + if err != nil { + glog.Fatal("Unable to parse endpoint url template") + return nil + } + return &SilverMobAdapter{endpoint: *template} +} + +func GetHeaders(request *openrtb.BidRequest) *http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return &headers +} + +func (a *SilverMobAdapter) MakeRequests( + openRTBRequest *openrtb.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + []*adapters.RequestData, + []error, +) { + requestCopy := *openRTBRequest + impCount := len(openRTBRequest.Imp) + requestData := make([]*adapters.RequestData, 0, impCount) + errs := []error{} + + var err error + + for _, imp := range openRTBRequest.Imp { + var silvermobExt *openrtb_ext.ExtSilverMob + + silvermobExt, err = a.getImpressionExt(&imp) + + if err != nil { + errs = append(errs, err) + continue + } + + url, err := a.buildEndpointURL(silvermobExt) + if err != nil { + errs = append(errs, err) + continue + } + + requestCopy.Imp = []openrtb.Imp{imp} + reqJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + reqData := &adapters.RequestData{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + Headers: *GetHeaders(&requestCopy), + } + + requestData = append(requestData, reqData) + } + + return requestData, errs +} + +func (a *SilverMobAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSilverMob, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("error unmarshaling imp.ext: %s", err.Error()), + } + } + var silvermobExt openrtb_ext.ExtSilverMob + if err := json.Unmarshal(bidderExt.Bidder, &silvermobExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("error unmarshaling imp.ext.bidder: %s", err.Error()), + } + } + return &silvermobExt, nil +} + +func (a *SilverMobAdapter) buildEndpointURL(params *openrtb_ext.ExtSilverMob) (string, error) { + endpointParams := macros.EndpointTemplateParams{ZoneID: params.ZoneID, Host: params.Host} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *SilverMobAdapter) MakeBids( + openRTBRequest *openrtb.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request status code: %d. Run with request.debug = 1 for more info", bidderRawResponse.StatusCode), + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderRawResponse.StatusCode)} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error unmarshaling server Response: %s", err), + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range bidResp.SeatBid { + for _, bid := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, openRTBRequest.Imp), + }) + } + } + + return bidResponse, nil +} + +func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } + return mediaType + } + } + return mediaType +} diff --git a/adapters/silvermob/silvermob_test.go b/adapters/silvermob/silvermob_test.go new file mode 100644 index 00000000000..f75b16fe3c2 --- /dev/null +++ b/adapters/silvermob/silvermob_test.go @@ -0,0 +1,11 @@ +package silvermob + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "silvermobtest", NewSilverMobBidder("http://{{.Host}}.example.com/api/dsp/bid/{{.ZoneID}}")) +} diff --git a/adapters/silvermob/silvermobtest/exemplary/banner-app.json b/adapters/silvermob/silvermobtest/exemplary/banner-app.json new file mode 100644 index 00000000000..7a2a4fef26e --- /dev/null +++ b/adapters/silvermob/silvermobtest/exemplary/banner-app.json @@ -0,0 +1,163 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w":320, + "h":50 + } + ], + "type": "banner", + "seat": "silvermob" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid":{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w":320, + "h":50 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/exemplary/banner-multi-app.json b/adapters/silvermob/silvermobtest/exemplary/banner-multi-app.json new file mode 100644 index 00000000000..531704c5c29 --- /dev/null +++ b/adapters/silvermob/silvermobtest/exemplary/banner-multi-app.json @@ -0,0 +1,293 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + }, + { + "id": "another-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":480 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w":320, + "h":50 + } + ], + "type": "banner", + "seat": "silvermob" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "another-impression-id", + "banner": { + "w":320, + "h":480 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "1" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "another-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "21", + "w":320, + "h":480 + } + ], + "type": "banner", + "seat": "silvermob" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + + { + "bids":[ + { + "bid":{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w":320, + "h":50 + }, + "type": "banner" + } + ] + }, + { + "bids": [ + { + "bid":{ + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "another-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "21", + "w":320, + "h":480 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/exemplary/native-app.json b/adapters/silvermob/silvermobtest/exemplary/native-app.json new file mode 100644 index 00000000000..49934bf75ab --- /dev/null +++ b/adapters/silvermob/silvermobtest/exemplary/native-app.json @@ -0,0 +1,159 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "type": "native", + "seat": "silvermob" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "asesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/exemplary/video-app.json b/adapters/silvermob/silvermobtest/exemplary/video-app.json new file mode 100644 index 00000000000..c80d5ab900a --- /dev/null +++ b/adapters/silvermob/silvermobtest/exemplary/video-app.json @@ -0,0 +1,171 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "silvermob" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "asesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/silvermob/silvermobtest/params/race/banner.json b/adapters/silvermob/silvermobtest/params/race/banner.json new file mode 100644 index 00000000000..9b6ca9d749b --- /dev/null +++ b/adapters/silvermob/silvermobtest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "zoneid": "0", + "host": "eu" +} diff --git a/adapters/silvermob/silvermobtest/params/race/native.json b/adapters/silvermob/silvermobtest/params/race/native.json new file mode 100644 index 00000000000..f63a4842b6d --- /dev/null +++ b/adapters/silvermob/silvermobtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "zoneid": "0", + "host": "eu" +} \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/params/race/video.json b/adapters/silvermob/silvermobtest/params/race/video.json new file mode 100644 index 00000000000..f63a4842b6d --- /dev/null +++ b/adapters/silvermob/silvermobtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "zoneid": "0", + "host": "eu" +} \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/supplemental/empty-seatbid-array.json b/adapters/silvermob/silvermobtest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..be95abeaa2f --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/empty-seatbid-array.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "silvermob": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-response.json b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json new file mode 100644 index 00000000000..d2a1e890df0 --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json @@ -0,0 +1,118 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Error unmarshaling server Response: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-silvermob-ext-object.json b/adapters/silvermob/silvermobtest/supplemental/invalid-silvermob-ext-object.json new file mode 100644 index 00000000000..090d7aff5b6 --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-silvermob-ext-object.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "Awesome" + } + ], + "site": { + "page": "test.com" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "error unmarshaling imp.ext: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/status-code-bad-request.json b/adapters/silvermob/silvermobtest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..e93f249030d --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/status-code-bad-request.json @@ -0,0 +1,99 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID", + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Bad Request status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/status-code-no-content.json b/adapters/silvermob/silvermobtest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..a29710bfa85 --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/status-code-no-content.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/status-code-other-error.json b/adapters/silvermob/silvermobtest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..a32af01e55f --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/status-code-other-error.json @@ -0,0 +1,87 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 306. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/silvermob/silvermobtest/supplemental/status-code-service-unavailable.json b/adapters/silvermob/silvermobtest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..5772a86ee8a --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,87 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://eu.example.com/api/dsp/bid/0", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "host": "eu", + "zoneid": "0" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.awesome.app", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index c2db7b7d03c..7d1fac3c633 100755 --- a/config/config.go +++ b/config/config.go @@ -992,6 +992,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1") + v.SetDefault("adapters.silvermob.endpoint", "http://{{.Host}}.silvermob.com/marketplace/api/dsp/bid/{{.ZoneID}}") v.SetDefault("adapters.smaato.endpoint", "https://prebid.ad.smaato.net/oapi/prebid") v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index ff72165bcb3..9800ccd7e28 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -74,6 +74,7 @@ import ( "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/silvermob" "github.com/prebid/prebid-server/adapters/smaato" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" @@ -176,6 +177,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), openrtb_ext.BidderSharethrough: sharethrough.NewSharethroughBidder(cfg.Adapters[string(openrtb_ext.BidderSharethrough)].Endpoint), + openrtb_ext.BidderSilverMob: silvermob.NewSilverMobBidder(cfg.Adapters[string(openrtb_ext.BidderSilverMob)].Endpoint), openrtb_ext.BidderSmaato: smaato.NewSmaatoBidder(cfg.Adapters[string(openrtb_ext.BidderSmaato)].Endpoint), openrtb_ext.BidderSmartadserver: smartadserver.NewSmartadserverBidder(cfg.Adapters[string(openrtb_ext.BidderSmartadserver)].Endpoint), openrtb_ext.BidderSmartRTB: smartrtb.NewSmartRTBBidder(cfg.Adapters[string(openrtb_ext.BidderSmartRTB)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index b98c80ae9af..0fac0800ee3 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -93,6 +93,7 @@ const ( BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" + BidderSilverMob BidderName = "silvermob" BidderSmaato BidderName = "smaato" BidderSmartadserver BidderName = "smartadserver" BidderSmartRTB BidderName = "smartrtb" @@ -187,6 +188,7 @@ var BidderMap = map[string]BidderName{ "rtbhouse": BidderRTBHouse, "rubicon": BidderRubicon, "sharethrough": BidderSharethrough, + "silvermob": BidderSilverMob, "smaato": BidderSmaato, "smartadserver": BidderSmartadserver, "smartrtb": BidderSmartRTB, diff --git a/openrtb_ext/imp_silvermob.go b/openrtb_ext/imp_silvermob.go new file mode 100644 index 00000000000..9b2465534ca --- /dev/null +++ b/openrtb_ext/imp_silvermob.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtSilverMob defines the contract for bidrequest.imp[i].ext.silvermob +type ExtSilverMob struct { + ZoneID string `json:"zoneid"` + Host string `json:"host"` +} diff --git a/static/bidder-info/silvermob.yaml b/static/bidder-info/silvermob.yaml new file mode 100644 index 00000000000..5f1e4809dd3 --- /dev/null +++ b/static/bidder-info/silvermob.yaml @@ -0,0 +1,8 @@ +maintainer: + email: "support@silvermob.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/silvermob.json b/static/bidder-params/silvermob.json new file mode 100644 index 00000000000..8ebc85a2ab7 --- /dev/null +++ b/static/bidder-params/silvermob.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SilverMob Adapter Params", + "description": "A schema which validates params accepted by the SilverMob adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "string", + "description": "Zone ID" + }, + "host": { + "type": "string", + "description": "Host" + } + }, + "required": ["zoneid", "host"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7582055fa46..a6e74966ad7 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -102,6 +102,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderMobileFuse: true, openrtb_ext.BidderOrbidder: true, openrtb_ext.BidderPubnative: true, + openrtb_ext.BidderSilverMob: true, openrtb_ext.BidderSmaato: true, openrtb_ext.BidderTappx: true, openrtb_ext.BidderYeahmobi: true, From 9a3f2a042aff31127037d5f80531edee6fd560d3 Mon Sep 17 00:00:00 2001 From: Seba Perez Date: Wed, 11 Nov 2020 15:28:33 -0300 Subject: [PATCH 249/603] Updated ePlanning GVL ID (#1574) --- adapters/eplanning/usersync.go | 2 +- adapters/eplanning/usersync_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/eplanning/usersync.go b/adapters/eplanning/usersync.go index 252c106a77c..faa7fa82a19 100644 --- a/adapters/eplanning/usersync.go +++ b/adapters/eplanning/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewEPlanningSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("eplanning", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("eplanning", 90, temp, adapters.SyncTypeIframe) } diff --git a/adapters/eplanning/usersync_test.go b/adapters/eplanning/usersync_test.go index 890832bafc3..85770689024 100644 --- a/adapters/eplanning/usersync_test.go +++ b/adapters/eplanning/usersync_test.go @@ -20,6 +20,6 @@ func TestEPlanningSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3Flocalhost%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.EqualValues(t, 90, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } From aaecdfada00fa657fcb4237ea2c4caf3b2f664e6 Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 11 Nov 2020 22:02:59 +0100 Subject: [PATCH 250/603] update adpone google vendor id (#1577) --- adapters/adpone/usersync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/adpone/usersync.go b/adapters/adpone/usersync.go index 480ecb82f3f..67d4c998275 100644 --- a/adapters/adpone/usersync.go +++ b/adapters/adpone/usersync.go @@ -7,7 +7,7 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const adponeGDPRVendorID = uint16(16) +const adponeGDPRVendorID = uint16(799) const adponeFamilyName = "adpone" func NewadponeSyncer(urlTemplate *template.Template) usersync.Usersyncer { From 70600225899ce1b55e1ea19a6c07d1172b1bd642 Mon Sep 17 00:00:00 2001 From: Gena Date: Thu, 12 Nov 2020 17:43:05 +0200 Subject: [PATCH 251/603] ADtelligent gvlid (#1581) --- adapters/adtelligent/usersync.go | 2 +- adapters/adtelligent/usersync_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/adtelligent/usersync.go b/adapters/adtelligent/usersync.go index cda3b62a071..087b5bdd22d 100644 --- a/adapters/adtelligent/usersync.go +++ b/adapters/adtelligent/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdtelligentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtelligent", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtelligent", 410, temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adtelligent/usersync_test.go b/adapters/adtelligent/usersync_test.go index 1cc5dfe4627..fa157d226c5 100644 --- a/adapters/adtelligent/usersync_test.go +++ b/adapters/adtelligent/usersync_test.go @@ -25,6 +25,6 @@ func TestAdtelligentSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.EqualValues(t, 410, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } From 12d96a6ef68161e0db498215d99410d5d64cc331 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 12 Nov 2020 13:54:35 -0500 Subject: [PATCH 252/603] Add account/ host GDPR enabled flags & account per request type GDPR enabled flags (#1564) * Add account level request type specific and general GDPR enabled flags * Clean up test TestAccountLevelGDPREnabled * Add host-level GDPR enabled flag * Move account GDPR enable check as receiver method on accountGDPR * Remove mapstructure annotations on account structs * Minor test updates * Re-add mapstructure annotations on account structs * Change RequestType to IntegrationType and struct annotation formatting * Update comment * Update account IntegrationType comments * Remove extra space in config/accounts.go via gofmt --- config/accounts.go | 52 ++++++++ config/accounts_test.go | 116 +++++++++++++++++ config/config.go | 2 + exchange/exchange.go | 2 +- exchange/exchange_test.go | 2 + .../exchangetest/gdpr-geo-eu-off-device.json | 1 + exchange/exchangetest/gdpr-geo-eu-off.json | 1 + .../gdpr-geo-eu-on-featureflag-off.json | 62 +++++++++ exchange/exchangetest/gdpr-geo-eu-on.json | 1 + exchange/utils.go | 23 +++- exchange/utils_test.go | 123 ++++++++++++++---- 11 files changed, 358 insertions(+), 27 deletions(-) create mode 100644 config/accounts_test.go create mode 100644 exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json diff --git a/config/accounts.go b/config/accounts.go index 162818eb95d..5ec818b843f 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -1,9 +1,61 @@ package config +// IntegrationType enumerates the values of integrations Prebid Server can configure for an account +type IntegrationType string + +// Possible values of integration types Prebid Server can configure for an account +const ( + IntegrationTypeAMP IntegrationType = "amp" + IntegrationTypeApp IntegrationType = "app" + IntegrationTypeVideo IntegrationType = "video" + IntegrationTypeWeb IntegrationType = "web" +) + // Account represents a publisher account configuration type Account struct { ID string `mapstructure:"id" json:"id"` Disabled bool `mapstructure:"disabled" json:"disabled"` CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` + GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` +} + +// AccountGDPR represents account-specific GDPR configuration +type AccountGDPR struct { + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + IntegrationEnabled AccountGDPRIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` +} + +// EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type +// by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil +func (a *AccountGDPR) EnabledForIntegrationType(integrationType IntegrationType) *bool { + var integrationEnabled *bool + + switch integrationType { + case IntegrationTypeAMP: + integrationEnabled = a.IntegrationEnabled.AMP + case IntegrationTypeApp: + integrationEnabled = a.IntegrationEnabled.App + case IntegrationTypeVideo: + integrationEnabled = a.IntegrationEnabled.Video + case IntegrationTypeWeb: + integrationEnabled = a.IntegrationEnabled.Web + } + + if integrationEnabled != nil { + return integrationEnabled + } + if a.Enabled != nil { + return a.Enabled + } + + return nil +} + +// AccountGDPRIntegration indicates whether GDPR is enabled for each integration type +type AccountGDPRIntegration struct { + AMP *bool `mapstructure:"amp" json:"amp,omitempty"` + App *bool `mapstructure:"app" json:"app,omitempty"` + Video *bool `mapstructure:"video" json:"video,omitempty"` + Web *bool `mapstructure:"web" json:"web,omitempty"` } diff --git a/config/accounts_test.go b/config/accounts_test.go new file mode 100644 index 00000000000..ca7e893835f --- /dev/null +++ b/config/accounts_test.go @@ -0,0 +1,116 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAccountGDPREnabledForIntegrationType(t *testing.T) { + trueValue, falseValue := true, false + + tests := []struct { + description string + giveIntegrationType IntegrationType + giveGDPREnabled *bool + giveAMPGDPREnabled *bool + giveAppGDPREnabled *bool + giveVideoGDPREnabled *bool + giveWebGDPREnabled *bool + wantEnabled *bool + }{ + { + description: "GDPR AMP integration enabled, general GDPR disabled", + giveIntegrationType: IntegrationTypeAMP, + giveGDPREnabled: &falseValue, + giveAMPGDPREnabled: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "GDPR App integration enabled, general GDPR disabled", + giveIntegrationType: IntegrationTypeApp, + giveGDPREnabled: &falseValue, + giveAppGDPREnabled: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "GDPR Video integration enabled, general GDPR disabled", + giveIntegrationType: IntegrationTypeVideo, + giveGDPREnabled: &falseValue, + giveVideoGDPREnabled: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "GDPR Web integration enabled, general GDPR disabled", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "Web integration enabled, general GDPR unspecified", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: &trueValue, + wantEnabled: &trueValue, + }, + { + description: "GDPR Web integration disabled, general GDPR enabled", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: &falseValue, + wantEnabled: &falseValue, + }, + { + description: "GDPR Web integration disabled, general GDPR unspecified", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: &falseValue, + wantEnabled: &falseValue, + }, + { + description: "GDPR Web integration unspecified, general GDPR disabled", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: nil, + wantEnabled: &falseValue, + }, + { + description: "GDPR Web integration unspecified, general GDPR enabled", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: nil, + wantEnabled: &trueValue, + }, + { + description: "GDPR Web integration unspecified, general GDPR unspecified", + giveIntegrationType: IntegrationTypeWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: nil, + wantEnabled: nil, + }, + } + + for _, tt := range tests { + account := Account{ + GDPR: AccountGDPR{ + Enabled: tt.giveGDPREnabled, + IntegrationEnabled: AccountGDPRIntegration{ + AMP: tt.giveAMPGDPREnabled, + App: tt.giveAppGDPREnabled, + Video: tt.giveVideoGDPREnabled, + Web: tt.giveWebGDPREnabled, + }, + }, + } + + enabled := account.GDPR.EnabledForIntegrationType(tt.giveIntegrationType) + + if tt.wantEnabled == nil { + assert.Nil(t, enabled, tt.description) + } else { + assert.NotNil(t, enabled, tt.description) + assert.Equal(t, *tt.wantEnabled, *enabled, tt.description) + } + } +} diff --git a/config/config.go b/config/config.go index 7d1fac3c633..045aea3c7ab 100755 --- a/config/config.go +++ b/config/config.go @@ -210,6 +210,7 @@ type Privacy struct { } type GDPR struct { + Enabled bool `mapstructure:"enabled"` HostVendorID int `mapstructure:"host_vendor_id"` UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` @@ -1028,6 +1029,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("analytics.pubstack.buffers.count", 100) v.SetDefault("analytics.pubstack.buffers.timeout", "900s") v.SetDefault("amp_timeout_adjustment_ms", 0) + v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) v.SetDefault("gdpr.usersync_if_ambiguous", false) v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) diff --git a/exchange/exchange.go b/exchange/exchange.go index 2e8fe76fcfa..3a5f785bbc8 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -122,7 +122,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels) - cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, usersyncIfAmbiguous, e.privacyConfig) + cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, account) e.me.RecordRequestPrivacy(privacyLabels) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ae8bc3c8c9d..bd2fa1147ef 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1305,6 +1305,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Enforce: spec.EnforceLMT, }, GDPR: config.GDPR{ + Enabled: spec.GDPREnabled, UsersyncIfAmbiguous: !spec.AssumeGDPRApplies, EEACountriesMap: eeac, }, @@ -2444,6 +2445,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } type exchangeSpec struct { + GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"` Response exchangeResponse `json:"response,omitempty"` diff --git a/exchange/exchangetest/gdpr-geo-eu-off-device.json b/exchange/exchangetest/gdpr-geo-eu-off-device.json index fc655de8162..f704cdd5c8e 100644 --- a/exchange/exchangetest/gdpr-geo-eu-off-device.json +++ b/exchange/exchangetest/gdpr-geo-eu-off-device.json @@ -1,5 +1,6 @@ { "assume_gdpr_applies": false, + "gdpr_enabled": true, "incomingRequest": { "ortbRequest": { "id": "some-request-id", diff --git a/exchange/exchangetest/gdpr-geo-eu-off.json b/exchange/exchangetest/gdpr-geo-eu-off.json index 27a030f11fc..24357eb7eec 100644 --- a/exchange/exchangetest/gdpr-geo-eu-off.json +++ b/exchange/exchangetest/gdpr-geo-eu-off.json @@ -1,5 +1,6 @@ { "assume_gdpr_applies": false, + "gdpr_enabled": true, "incomingRequest": { "ortbRequest": { "id": "some-request-id", diff --git a/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json new file mode 100644 index 00000000000..6c6ca3edc62 --- /dev/null +++ b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json @@ -0,0 +1,62 @@ +{ + "assume_gdpr_applies": true, + "gdpr_enabled": false, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "FRA" + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "user": { + "buyeruid": "some-buyer-id", + "geo": { + "country": "FRA" + } + } + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "errors": ["appnexus-error"] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/gdpr-geo-eu-on.json b/exchange/exchangetest/gdpr-geo-eu-on.json index 4ec42fc6c70..eb42a17c936 100644 --- a/exchange/exchangetest/gdpr-geo-eu-on.json +++ b/exchange/exchangetest/gdpr-geo-eu-on.json @@ -1,5 +1,6 @@ { "assume_gdpr_applies": true, + "gdpr_enabled": true, "incomingRequest": { "ortbRequest": { "id": "some-request-id", diff --git a/exchange/utils.go b/exchange/utils.go index 63e56e841c3..f6fcc711e42 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -19,6 +19,13 @@ import ( "github.com/prebid/prebid-server/privacy/lmt" ) +var integrationTypeMap = map[pbsmetrics.RequestType]config.IntegrationType{ + pbsmetrics.ReqTypeAMP: config.IntegrationTypeAMP, + pbsmetrics.ReqTypeORTB2App: config.IntegrationTypeApp, + pbsmetrics.ReqTypeVideo: config.IntegrationTypeVideo, + pbsmetrics.ReqTypeORTB2Web: config.IntegrationTypeWeb, +} + const unknownBidder string = "" func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { @@ -53,7 +60,8 @@ func cleanOpenRTBRequests(ctx context.Context, labels pbsmetrics.Labels, gDPR gdpr.Permissions, usersyncIfAmbiguous bool, - privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) { + privacyConfig config.Privacy, + account *config.Account) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) { impsByBidder, errs := splitImps(orig.Imp) if len(errs) > 0 { @@ -94,7 +102,9 @@ func cleanOpenRTBRequests(ctx context.Context, privacyLabels.COPPAEnforced = privacyEnforcement.COPPA privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder) - if gdpr == 1 { + gdprEnabled := gdprEnabled(account, privacyConfig, integrationTypeMap[labels.RType]) + + if gdpr == 1 && gdprEnabled { privacyLabels.GDPREnforced = true parsedConsent, err := vendorconsent.ParseString(consent) if err == nil { @@ -109,7 +119,7 @@ func cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidder.String()) // GDPR - if gdpr == 1 { + if gdpr == 1 && gdprEnabled { coreBidder := resolveBidder(bidder.String(), aliases) var publisherID = labels.PubID @@ -127,6 +137,13 @@ func cleanOpenRTBRequests(ctx context.Context, return } +func gdprEnabled(account *config.Account, privacyConfig config.Privacy, integrationType config.IntegrationType) bool { + if accountEnabled := account.GDPR.EnabledForIntegrationType(integrationType); accountEnabled != nil { + return *accountEnabled + } + return privacyConfig.GDPR.Enabled +} + func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases map[string]string) (privacy.PolicyEnforcer, error) { ccpaPolicy, err := ccpa.ReadFromRequest(orig) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 94975e82a46..14293ae20a3 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -82,7 +82,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, &config.Account{}) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -179,7 +179,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, &config.Account{}) result := results["appnexus"] assert.Nil(t, errs) @@ -229,7 +229,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } - _, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &reqExtStruct, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + _, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &reqExtStruct, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, &config.Account{}) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -264,7 +264,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { req := newBidRequest(t) req.Regs = &openrtb.Regs{COPPA: test.coppa} - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, &config.Account{}) result := results["appnexus"] assert.Nil(t, errs) @@ -366,7 +366,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { extRequest = unmarshaledExt } - results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, extRequest, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{}) + results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, extRequest, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{}, &config.Account{}) result := results["appnexus"] if test.hasError == true { @@ -943,7 +943,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, &config.Account{}) result := results["appnexus"] assert.Nil(t, errs) @@ -959,8 +959,12 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } func TestCleanOpenRTBRequestsGDPR(t *testing.T) { + trueValue, falseValue := true, false + testCases := []struct { description string + gdprAccountEnabled *bool + gdprHostEnabled bool gdpr string gdprConsent string gdprScrub bool @@ -968,40 +972,96 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { expectPrivacyLabels pbsmetrics.PrivacyLabels }{ { - description: "Enforce - TCF Invalid", - gdpr: "1", - gdprConsent: "malformed", - gdprScrub: false, + description: "Enforce - TCF Invalid", + gdprAccountEnabled: &trueValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: "malformed", + gdprScrub: false, expectPrivacyLabels: pbsmetrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", }, }, { - description: "Enforce - TCF 1", - gdpr: "1", - gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - gdprScrub: true, + description: "Enforce - TCF 1", + gdprAccountEnabled: &trueValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: true, expectPrivacyLabels: pbsmetrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: pbsmetrics.TCFVersionV1, }, }, { - description: "Enforce - TCF 2", - gdpr: "1", - gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - gdprScrub: true, + description: "Enforce - TCF 2", + gdprAccountEnabled: &trueValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + gdprScrub: true, expectPrivacyLabels: pbsmetrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: pbsmetrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1", - gdpr: "0", - gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - gdprScrub: false, + description: "Not Enforce - TCF 1", + gdprAccountEnabled: &trueValue, + gdprHostEnabled: true, + gdpr: "0", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: false, + GDPRTCFVersion: "", + }, + }, + { + description: "Enforce - TCF 1; account GDPR enabled, host GDPR setting disregarded", + gdprAccountEnabled: &trueValue, + gdprHostEnabled: false, + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV1, + }, + }, + { + description: "Not Enforce - TCF 1; account GDPR disabled, host GDPR setting disregarded", + gdprAccountEnabled: &falseValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: false, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: false, + GDPRTCFVersion: "", + }, + }, + { + description: "Enforce - TCF 1; account GDPR not specified, host GDPR enabled", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: true, + expectPrivacyLabels: pbsmetrics.PrivacyLabels{ + GDPREnforced: true, + GDPRTCFVersion: pbsmetrics.TCFVersionV1, + }, + }, + { + description: "Not Enforce - TCF 1; account GDPR not specified, host GDPR disabled", + gdprAccountEnabled: nil, + gdprHostEnabled: false, + gdpr: "1", + gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprScrub: false, expectPrivacyLabels: pbsmetrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1018,13 +1078,30 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ + Enabled: test.gdprHostEnabled, TCF2: config.TCF2{ Enabled: true, }, }, } - results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig) + accountConfig := config.Account{ + GDPR: config.AccountGDPR{ + Enabled: test.gdprAccountEnabled, + }, + } + + results, _, privacyLabels, errs := cleanOpenRTBRequests( + context.Background(), + req, + nil, + &emptyUsersync{}, + map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, + pbsmetrics.Labels{}, + &permissionsMock{personalInfoAllowed: !test.gdprScrub}, + true, + privacyConfig, + &accountConfig) result := results["appnexus"] assert.Nil(t, errs) From ada88b4f72dc3c298eec61242e527427c071a4fc Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Mon, 16 Nov 2020 07:47:51 -0500 Subject: [PATCH 253/603] DMX Bidfloor fix (#1579) --- adapters/dmx/dmx.go | 27 ++-- .../exemplary/imp-populated-banner.json | 139 ++++++++++++++++++ 2 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 adapters/dmx/dmxtest/exemplary/imp-populated-banner.json diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index dfb97f33abb..6da7823a75f 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -168,6 +168,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte Body: oJson, Headers: headers, } + reqsBidder = append(reqsBidder, reqBidder) return } @@ -221,35 +222,29 @@ func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest } func fetchParams(params dmxExt, inst openrtb.Imp, ins openrtb.Imp, imps []openrtb.Imp, banner *openrtb.Banner, video *openrtb.Video, intVal int8) []openrtb.Imp { + var tempimp openrtb.Imp + tempimp = inst if params.Bidder.TagId != "" { - ins = openrtb.Imp{ - ID: inst.ID, - TagID: params.Bidder.TagId, - Ext: inst.Ext, - Secure: &intVal, - } + tempimp.TagID = params.Bidder.TagId + tempimp.Secure = &intVal } if params.Bidder.DmxId != "" { - ins = openrtb.Imp{ - ID: inst.ID, - TagID: params.Bidder.DmxId, - Ext: inst.Ext, - Secure: &intVal, - } + tempimp.TagID = params.Bidder.DmxId + tempimp.Secure = &intVal } if banner != nil { - ins.Banner = banner + tempimp.Banner = banner } if video != nil { - ins.Video = video + tempimp.Video = video } - if ins.TagID == "" { + if tempimp.TagID == "" { return imps } - imps = append(imps, ins) + imps = append(imps, tempimp) return imps } diff --git a/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json b/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json new file mode 100644 index 00000000000..f2b30bb400b --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + "bidfloor": 0.35, + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "dmxid": "123454", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + "bidfloor": 0.35, + "id": "test-imp-id", + "tagid": "123454", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisher_id": "10400", + "dmxid": "123454" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adid": "29681110", + "adm": "
banner-ads
", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adm": "
banner-ads
", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file From acf889e881afdf1085a76e45e61879e69f931320 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Tue, 17 Nov 2020 18:39:18 +0200 Subject: [PATCH 254/603] adform bidder video bid response support (#1573) --- adapters/adform/adform.go | 25 +++-- adapters/adform/adform_test.go | 67 ++++++++++--- .../exemplary/multiformat-impression.json | 99 +++++++++++++++++++ .../exemplary/single-banner-impression.json | 64 ++++++++++++ .../exemplary/single-video-impression.json | 60 +++++++++++ .../adform/adformtest/params/race/video.json | 3 + static/bidder-info/adform.yaml | 2 + 7 files changed, 302 insertions(+), 18 deletions(-) create mode 100644 adapters/adform/adformtest/exemplary/multiformat-impression.json create mode 100644 adapters/adform/adformtest/exemplary/single-banner-impression.json create mode 100644 adapters/adform/adformtest/exemplary/single-video-impression.json create mode 100644 adapters/adform/adformtest/params/race/video.json diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 5881f4ab86e..d7e5dedac22 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -79,6 +79,7 @@ type adformBid struct { Height uint64 `json:"height,omitempty"` DealId string `json:"deal_id,omitempty"` CreativeId string `json:"win_crid,omitempty"` + VastContent string `json:"vast_content,omitempty"` } const priceTypeGross = "gross" @@ -241,7 +242,8 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { bids := make(pbs.PBSBidSlice, 0) for i, bid := range adformBids { - if bid.Banner == "" || bid.ResponseType != "banner" { + adm, bidType := getAdAndType(bid) + if adm == "" { continue } pbsBid := pbs.PBSBid{ @@ -249,12 +251,12 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { AdUnitCode: r.adUnits[i].adUnitCode, BidderCode: r.bidderCode, Price: bid.Price, - Adm: bid.Banner, + Adm: adm, Width: bid.Width, Height: bid.Height, DealId: bid.DealId, Creative_id: bid.CreativeId, - CreativeMediaType: string(openrtb_ext.BidTypeBanner), + CreativeMediaType: string(bidType), } bids = append(bids, &pbsBid) @@ -632,21 +634,23 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt } for i, bid := range adformBids { - if bid.Banner == "" || bid.ResponseType != "banner" { + adm, bidType := getAdAndType(bid) + if adm == "" { continue } + openRtbBid := openrtb.Bid{ ID: r.Imp[i].ID, ImpID: r.Imp[i].ID, Price: bid.Price, - AdM: bid.Banner, + AdM: adm, W: bid.Width, H: bid.Height, DealID: bid.DealId, CrID: bid.CreativeId, } - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: openrtb_ext.BidTypeBanner}) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: bidType}) currency = bid.Currency } @@ -654,3 +658,12 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt return bidResponse } + +func getAdAndType(bid *adformBid) (string, openrtb_ext.BidType) { + if bid.ResponseType == "banner" { + return bid.Banner, openrtb_ext.BidTypeBanner + } else if bid.ResponseType == "vast_content" { + return bid.VastContent, openrtb_ext.BidTypeVideo + } + return "", "" +} diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index f227776207d..0bfb95b6369 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -107,6 +107,16 @@ func createAdformServerResponse(testData aBidInfo) ([]byte, error) { DealId: testData.tags[2].dealId, CreativeId: testData.tags[2].creativeId, }, + { + ResponseType: "vast_content", + VastContent: testData.tags[3].content, + Price: testData.tags[3].price, + Currency: "EUR", + Width: testData.width, + Height: testData.height, + DealId: testData.tags[3].dealId, + CreativeId: testData.tags[3].creativeId, + }, } adformServerResponse, err := json.Marshal(bids) return adformServerResponse, err @@ -123,10 +133,21 @@ func TestAdformBasicResponse(t *testing.T) { if err != nil { t.Fatalf("Should not have gotten adapter error: %v", err) } - if len(bids) != 2 { - t.Fatalf("Received %d bids instead of 2", len(bids)) + if len(bids) != 3 { + t.Fatalf("Received %d bids instead of 3", len(bids)) + } + expectedTypes := []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeVideo, } - for _, bid := range bids { + + for i, bid := range bids { + + if bid.CreativeMediaType != string(expectedTypes[i]) { + t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType) + } + matched := false for _, tag := range adformTestData.tags { if bid.AdUnitCode == tag.code { @@ -224,7 +245,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { prebidRequest := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 3), + AdUnits: make([]pbs.AdUnit, 4), Device: &openrtb.Device{ UA: requestData.deviceUA, IP: requestData.deviceIP, @@ -326,6 +347,7 @@ func createTestData(secure bool) aBidInfo { {mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", cdims: "300x300,400x200", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"}, {mid: 32345, priceType: "net", code: "code2", minp: 23.1, cdims: "300x200"}, // no bid for ad unit {mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2", url: "https://adform.com?a=b"}, + {mid: 32347, code: "code4", content: "vast-xml"}, }, secure: secure, currency: "EUR", @@ -379,6 +401,11 @@ func createOpenRtbRequest(testData *aBidInfo) *openrtb.BidRequest { func TestOpenRTBStandardResponse(t *testing.T) { testData := createTestData(true) request := createOpenRtbRequest(&testData) + expectedTypes := []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeVideo, + } responseBody, err := createAdformServerResponse(testData) if err != nil { @@ -390,16 +417,17 @@ func TestOpenRTBStandardResponse(t *testing.T) { bidder := new(AdformAdapter) bidResponse, errs := bidder.MakeBids(request, nil, httpResponse) - if len(bidResponse.Bids) != 2 { - t.Fatalf("Expected 2 bids. Got %d", len(bidResponse.Bids)) + if len(bidResponse.Bids) != 3 { + t.Fatalf("Expected 3 bids. Got %d", len(bidResponse.Bids)) } if len(errs) != 0 { t.Errorf("Expected 0 errors. Got %d", len(errs)) } - for _, typeBid := range bidResponse.Bids { - if typeBid.BidType != openrtb_ext.BidTypeBanner { - t.Errorf("Expected a banner bid. Got: %s", bidResponse.Bids[0].BidType) + for i, typeBid := range bidResponse.Bids { + + if typeBid.BidType != expectedTypes[i] { + t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], typeBid.BidType) } bid := typeBid.Bid matched := false @@ -561,10 +589,10 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo var midsWithCurrency = "" var queryString = "" if isOpenRtb { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS" + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency } else { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency } @@ -646,7 +674,7 @@ func TestPriceTypeUrlParameterCreation(t *testing.T) { // Asserts that toOpenRtbBidResponse() creates a *adapters.BidderResponse with // the currency of the last valid []*adformBid element and the expected number of bids func TestToOpenRtbBidResponse(t *testing.T) { - expectedBids := 3 + expectedBids := 4 lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", "" request := &openrtb.BidRequest{ @@ -672,6 +700,11 @@ func TestToOpenRtbBidResponse(t *testing.T) { Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`), Banner: &openrtb.Banner{}, }, + { + ID: "video-imp-no4", + Ext: json.RawMessage(`{"bidder1": { "mid": "32345" }}`), + Banner: &openrtb.Banner{}, + }, }, Device: &openrtb.Device{UA: "ua", IP: "ip"}, User: &openrtb.User{BuyerUID: "buyerUID"}, @@ -703,6 +736,16 @@ func TestToOpenRtbBidResponse(t *testing.T) { ResponseType: "banner", Banner: "banner-content4", Price: 1.25, + Currency: emptyCurrency, + Width: 300, + Height: 200, + DealId: "dealId4", + CreativeId: "creativeId4", + }, + { + ResponseType: "vast_content", + VastContent: "vast-content", + Price: 1.25, Currency: lastCurrency, Width: 300, Height: 200, diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json new file mode 100644 index 00000000000..efd4aca63e2 --- /dev/null +++ b/adapters/adform/adformtest/exemplary/multiformat-impression.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }, + { + "id": "video-imp-id", + "video": { + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "mid": 54321 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "response": "banner", + "banner": "", + "win_bid": 0.5, + "win_cur": "USD", + "width": 300, + "height": 250, + "deal_id": null, + "win_crid": "20078830" + }, + { + "response": "vast_content", + "vast_content": "", + "win_bid": 0.7, + "win_cur": "USD", + "width": 640, + "height": 480, + "deal_id": "DID-123-22", + "win_crid": "20078831" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "banner-imp-id", + "impid": "banner-imp-id", + "price": 0.5, + "adm": "", + "crid": "20078830", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "video-imp-id", + "impid": "video-imp-id", + "price": 0.7, + "adm": "", + "crid": "20078831", + "dealid": "DID-123-22", + "w": 640, + "h": 480 + }, + "type": "video" + } + ] + } + ] + } diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json new file mode 100644 index 00000000000..fd7f3cde526 --- /dev/null +++ b/adapters/adform/adformtest/exemplary/single-banner-impression.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "response": "banner", + "banner": "", + "win_bid": 0.5, + "win_cur": "USD", + "width": 300, + "height": 250, + "deal_id": null, + "win_crid": "20078830" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "", + "crid": "20078830", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json new file mode 100644 index 00000000000..e22977e6523 --- /dev/null +++ b/adapters/adform/adformtest/exemplary/single-video-impression.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "mid": 54321 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "response": "vast_content", + "vast_content": "", + "win_bid": 0.5, + "win_cur": "USD", + "width": 640, + "height": 480, + "deal_id": null, + "win_crid": "20078830" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "", + "crid": "20078830", + "w": 640, + "h": 480 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adform/adformtest/params/race/video.json b/adapters/adform/adformtest/params/race/video.json new file mode 100644 index 00000000000..51f8f1b94d2 --- /dev/null +++ b/adapters/adform/adformtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "mid": "858300" +} diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 8aafd9f6815..4dce10b9af8 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -4,6 +4,8 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner + - video From 17f50208439141bc14aa02fd34edc25b98a94375 Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Tue, 17 Nov 2020 11:41:50 -0500 Subject: [PATCH 255/603] Fix Beachfront JSON tests (#1578) --- .../exemplary/minimal-banner.json | 43 ++-- .../exemplary/simple-adm-video.json | 100 ++++---- .../beachfronttest/exemplary/simple-mix.json | 136 ++++++----- .../exemplary/simple-nurl-video.json | 130 ++++++----- .../minimal-banner-empty_array-200.json | 2 +- .../supplemental/minimal-mobile-video.json | 112 ++++----- .../supplemental/minimal-site-banner.json | 171 +++++++------- .../supplemental/mobile-banner.json | 28 ++- .../supplemental/multi-banner.json | 24 +- .../supplemental/multi-video.json | 214 ++++++++---------- .../supplemental/unmarshal-error-banner.json | 2 +- ...nmarshal-error-but-another-good-video.json | 2 +- .../supplemental/unmarshal-error-video.json | 2 +- 13 files changed, 462 insertions(+), 504 deletions(-) diff --git a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json index 51ce4e9295e..6672e2af91d 100644 --- a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json @@ -24,7 +24,6 @@ } ] }, - "httpCalls": [ { "expectedRequest": { @@ -57,38 +56,40 @@ "ua": "", "adapterName": "BF_PREBID_S2S", "adapterVersion": "0.9.0", - "user": { - } + "user": {} } }, "mockResponse": { "status": 200, "body": [ { - "crid":"crid_1", - "price":2.942808, - "w":300, - "h":250, - "slot":"div-gpt-ad-1460505748561-0", - "adm":"
", - "id": "some_test_ad_id_1", - "impid": "some_test_ad_id_1", - "ttl": 300, - "crid": "94395500", - "w": 300, - "price": 2.942808, - "adid": "94395500", - "h": 250 - }, - "type": "banner" - }, + "expectedBidResponses": [ { - "bid": { - "adm": "00:00:15", - "id": "some_test_ad_id_2", - "impid": "some_test_ad_id_2", - "ttl": 300, - "crid": "9999999", - "w": 1020, - "price": 1, - "adid": "9999999", - "h": 1000 + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" }, - "type": "video" + { + "bid": { + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + }, + "type": "video" + }] } ] } diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json index c2b20cf1c5d..f47fd66784e 100644 --- a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json +++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json @@ -167,33 +167,33 @@ } }], - "expectedBids": [{ - "bid": { - "adm": "
", - "id": "some_test_ad_id_1", - "impid": "some_test_ad_id_1", - "ttl": 300, - "crid": "94395500", - "w": 300, - "price": 2.942808, - "adid": "94395500", - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "adm": "00:00:15", - "id": "some_test_ad_id_2", - "impid": "some_test_ad_id_2", - "ttl": 300, - "crid": "9999999", - "w": 1020, - "price": 1, - "adid": "9999999", - "h": 1000 + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" }, - "type": "video" + { + "bid": { + "adm": "00:00:15", + "id": "some_test_ad_id_2", + "impid": "some_test_ad_id_2", + "crid": "9999999", + "w": 1020, + "price": 1, + "adid": "9999999", + "h": 1000 + }, + "type": "video" + }] } ] } diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json index 8de90f52192..20c62402fc1 100644 --- a/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json +++ b/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json @@ -102,18 +102,19 @@ } }], - "expectedBids": [{ - "bid": { - "adm": "
", + "adomain": [ + "dell.com.au" + ], + "cid": "668", + "crid": "253510977", + "ext": { + }, + "h": 250, + "id": "8911104898220857797", + "impid": "6a362d3a9db4eba300x250", + "nurl": "https://1x1.a-mo.net/hbx/bwin", + "price": 0.50, + "w": 300 + }, + { + "adid": "253510977", + "adm": "", + "adomain": [ + "dell.com.au" + ], + "cid": "668", + "crid": "253510977", + "ext": {}, + "h": 250, + "id": "430444686086263488", + "impid": "6a362d3a9db4eba300x250", + "nurl": "https://1x1.a-mo.net/hbx/bwin", + "price": 0.50, + "w": 300 + } + ], + "group": 0 + } + ] + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adid": "253510977", + "adm": "", + "adomain": [ + "dell.com.au" + ], + "cid": "668", + "crid": "253510977", + "ext": {}, + "h": 250, + "id": "8911104898220857797", + "impid": "6a362d3a9db4eba300x250", + "nurl": "https://1x1.a-mo.net/hbx/bwin", + "price": 0.50, + "w": 300 + }, + "type": "banner" + }, + { + "bid": { + "adid": "253510977", + "adm": "", + "adomain": [ + "dell.com.au" + ], + "cid": "668", + "crid": "253510977", + "ext": {}, + "h": 250, + "id": "430444686086263488", + "impid": "6a362d3a9db4eba300x250", + "nurl": "https://1x1.a-mo.net/hbx/bwin", + "price": 0.50, + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/amx/amxtest/exemplary/video-simple.json b/adapters/amx/amxtest/exemplary/video-simple.json index 8fb3baa26d0..678722adf8c 100644 --- a/adapters/amx/amxtest/exemplary/video-simple.json +++ b/adapters/amx/amxtest/exemplary/video-simple.json @@ -93,7 +93,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1", "body": { "device": { "dnt": 0, diff --git a/adapters/amx/amxtest/exemplary/web-simple.json b/adapters/amx/amxtest/exemplary/web-simple.json index 74854f912ae..f75bd85acb0 100644 --- a/adapters/amx/amxtest/exemplary/web-simple.json +++ b/adapters/amx/amxtest/exemplary/web-simple.json @@ -98,7 +98,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1", "body": { "device": { "dnt": 0, diff --git a/adapters/amx/amxtest/supplemental/204-response.json b/adapters/amx/amxtest/supplemental/204-response.json index 09571a03569..f51347d34d2 100644 --- a/adapters/amx/amxtest/supplemental/204-response.json +++ b/adapters/amx/amxtest/supplemental/204-response.json @@ -47,7 +47,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1", "body": { "device": { "dnt": 0, diff --git a/adapters/amx/amxtest/supplemental/400-response.json b/adapters/amx/amxtest/supplemental/400-response.json index f10cea89718..bb20bc94e7c 100644 --- a/adapters/amx/amxtest/supplemental/400-response.json +++ b/adapters/amx/amxtest/supplemental/400-response.json @@ -47,7 +47,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1", "body": { "device": { "dnt": 0, diff --git a/adapters/amx/amxtest/supplemental/500-response.json b/adapters/amx/amxtest/supplemental/500-response.json index fe5d89930c8..c56a217ce2e 100644 --- a/adapters/amx/amxtest/supplemental/500-response.json +++ b/adapters/amx/amxtest/supplemental/500-response.json @@ -47,7 +47,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0", + "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1", "body": { "device": { "dnt": 0, From 3793d4c0471da364d657f1773dade1f8d572e0ed Mon Sep 17 00:00:00 2001 From: ShriprasadM Date: Thu, 28 Jan 2021 23:52:22 +0530 Subject: [PATCH 309/603] requestheaders: new parameter inside debug.httpcalls. to log request header details (#1659) * Added support for logging requestheaders inside httpCalls.requestheaders * Reverterd test case change * Modified outgoing mock request for appnexus, to send some request header information. Modified sample mock response such that ext.debug.httpcalls.appnexus.requestheaders will return the information of passed request headers * Addressed code review comments given by SyntaxNode. Also Moved RequestHeaders next to RequestBidy in openrtb_ext.ExtHttpCall Co-authored-by: Shriprasad --- exchange/bidder.go | 14 ++++++++------ exchange/bidder_test.go | 11 +++++++++++ .../request-multi-bidders-debug-info.json | 4 ++++ openrtb_ext/response.go | 9 +++++---- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index e4867b0eeac..6e84c260f03 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -310,17 +310,19 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { if httpInfo.err == nil { return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), - ResponseBody: string(httpInfo.response.Body), - Status: httpInfo.response.StatusCode, + Uri: httpInfo.request.Uri, + RequestBody: string(httpInfo.request.Body), + ResponseBody: string(httpInfo.response.Body), + Status: httpInfo.response.StatusCode, + RequestHeaders: httpInfo.request.Headers, } } else if httpInfo.request == nil { return &openrtb_ext.ExtHttpCall{} } else { return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), + Uri: httpInfo.request.Uri, + RequestBody: string(httpInfo.request.Body), + RequestHeaders: httpInfo.request.Headers, } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 8266daaa172..93aaf241a01 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -886,6 +886,9 @@ func TestBadRequestLogging(t *testing.T) { if ext.Status != 0 { t.Errorf("The Status code should be 0. Got %d", ext.Status) } + if len(ext.RequestHeaders) > 0 { + t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders) + } } // TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response. @@ -894,6 +897,9 @@ func TestBadResponseLogging(t *testing.T) { request: &adapters.RequestData{ Uri: "test.com", Body: []byte("request body"), + Headers: http.Header{ + "header-1": []string{"value-1"}, + }, }, err: errors.New("Bad response"), } @@ -910,6 +916,7 @@ func TestBadResponseLogging(t *testing.T) { if ext.Status != 0 { t.Errorf("The Status code should be 0. Got %d", ext.Status) } + assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"") } // TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful. @@ -918,6 +925,9 @@ func TestSuccessfulResponseLogging(t *testing.T) { request: &adapters.RequestData{ Uri: "test.com", Body: []byte("request body"), + Headers: http.Header{ + "header-1": []string{"value-1", "value-2"}, + }, }, response: &adapters.ResponseData{ StatusCode: 200, @@ -937,6 +947,7 @@ func TestSuccessfulResponseLogging(t *testing.T) { if ext.Status != info.response.StatusCode { t.Errorf("The Status code should be 0. Got %d", ext.Status) } + assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders) } func TestMobileNativeTypes(t *testing.T) { diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json index ec174f75b36..5061798bf38 100644 --- a/exchange/exchangetest/request-multi-bidders-debug-info.json +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -65,6 +65,7 @@ { "uri": "appnexusTest.com", "requestbody": "appnexusTestRequestBody", + "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] }, "responsebody": "appnexusTestResponseBody", "status": 200 } @@ -94,6 +95,7 @@ { "uri": "audienceNetworkTest.com", "requestbody": "audienceNetworkTestRequestBody", + "requestheaders": null, "responsebody": "audienceNetworkTestResponseBody", "status": 200 } @@ -149,6 +151,7 @@ { "uri": "appnexusTest.com", "requestbody": "appnexusTestRequestBody", + "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] }, "responsebody": "appnexusTestResponseBody", "status": 200 } @@ -157,6 +160,7 @@ { "uri": "audienceNetworkTest.com", "requestbody": "audienceNetworkTestRequestBody", + "requestheaders": null, "responsebody": "audienceNetworkTestResponseBody", "status": 200 } diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 589dad40113..02370d19376 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -55,10 +55,11 @@ type ExtBidderError struct { // ExtHttpCall defines the contract for a bidresponse.ext.debug.httpcalls.{bidder}[i] type ExtHttpCall struct { - Uri string `json:"uri"` - RequestBody string `json:"requestbody"` - ResponseBody string `json:"responsebody"` - Status int `json:"status"` + Uri string `json:"uri"` + RequestBody string `json:"requestbody"` + RequestHeaders map[string][]string `json:"requestheaders"` + ResponseBody string `json:"responsebody"` + Status int `json:"status"` } // CookieStatus describes the allowed values for bidresponse.ext.usersync.{bidder}.status From c276b58e543f9a8bd7b247f11c2a054d39122035 Mon Sep 17 00:00:00 2001 From: Anand Venkatraman Date: Fri, 29 Jan 2021 00:16:29 +0530 Subject: [PATCH 310/603] Updating pulsepoint adapter (#1663) --- adapters/pulsepoint/params_test.go | 59 ++++++ adapters/pulsepoint/pulsepoint.go | 185 ++++++++++++++++-- adapters/pulsepoint/pulsepoint_test.go | 34 +++- .../pulsepointtest/exemplary/banner-app.json | 101 ++++++++++ .../pulsepointtest/exemplary/banner.json | 101 ++++++++++ .../exemplary/empty-pub-node-app.json | 96 +++++++++ .../exemplary/empty-pub-node-site.json | 96 +++++++++ .../pulsepointtest/exemplary/multi-imps.json | 151 ++++++++++++++ .../pulsepointtest/exemplary/native.json | 97 +++++++++ .../pulsepointtest/exemplary/video.json | 105 ++++++++++ .../pulsepointtest/params/race/banner.json | 1 - .../pulsepointtest/params/race/native.json | 4 + .../pulsepointtest/params/race/video.json | 4 + .../supplemental/bad-bid-data.json | 90 +++++++++ .../supplemental/bad-input.json | 70 +++++++ .../supplemental/bad-prebid-params.json | 32 +++ .../supplemental/bad-prebid.ext.json | 27 +++ .../pulsepointtest/supplemental/error.json | 70 +++++++ .../supplemental/impid-mismatch.json | 87 ++++++++ .../pulsepointtest/supplemental/passback.json | 68 +++++++ exchange/adapter_builders.go | 2 + exchange/adapter_util.go | 9 +- exchange/adapter_util_test.go | 34 ++-- openrtb_ext/imp_pulsepoint.go | 9 + static/bidder-info/pulsepoint.yaml | 6 + static/bidder-params/pulsepoint.json | 7 +- 26 files changed, 1487 insertions(+), 58 deletions(-) create mode 100644 adapters/pulsepoint/params_test.go create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/banner.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/native.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/video.json create mode 100644 adapters/pulsepoint/pulsepointtest/params/race/native.json create mode 100644 adapters/pulsepoint/pulsepointtest/params/race/video.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/error.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/passback.json create mode 100644 openrtb_ext/imp_pulsepoint.go diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go new file mode 100644 index 00000000000..ac2b314b96f --- /dev/null +++ b/adapters/pulsepoint/params_test.go @@ -0,0 +1,59 @@ +package pulsepoint + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderPulsepoint, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected pulsepoint params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the pubmatic schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderPulsepoint, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected pulsepoint params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"cp":1000, "ct": 2000}`, + `{"cp":1001, "ct": 2001}`, + `{"cp":1001, "ct": 2001, "cf": "1x1"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"cp":"1000"}`, + `{"ct":"1000"}`, + `{"cp":1000}`, + `{"ct":1000}`, + `{"cp":1000, "ct":"1000"}`, + `{"cp":1000, "ct": "abcd"}`, + `{"cp":"abcd", "ct": 1000}`, + `{"cp":"1000.2", "ct": "1000.1"}`, +} diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 23f11f9a40a..b07a2cba66f 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,17 +1,19 @@ package pulsepoint import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" + + "bytes" + "context" + "io/ioutil" "strings" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -23,15 +25,169 @@ type PulsePointAdapter struct { URI string } +// Builds an instance of PulsePointAdapter +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &PulsePointAdapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + + var err error + pubID := "" + imps := make([]openrtb.Imp, 0, len(request.Imp)) + for i := 0; i < len(request.Imp); i++ { + imp := request.Imp[i] + var bidderExt adapters.ExtImpBidder + if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + var pulsepointExt openrtb_ext.ExtImpPulsePoint + if err = json.Unmarshal(bidderExt.Bidder, &pulsepointExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + // parse pubid and keep it for reference + if pubID == "" && pulsepointExt.PubID > 0 { + pubID = strconv.Itoa(pulsepointExt.PubID) + } + // tag id to be sent + imp.TagID = strconv.Itoa(pulsepointExt.TagID) + imps = append(imps, imp) + } + + // verify there are valid impressions + if len(imps) == 0 { + return nil, errs + } + + // add the publisher id from ext to the site.pub.id or app.pub.id + if request.Site != nil { + site := *request.Site + if site.Publisher != nil { + publisher := *site.Publisher + publisher.ID = pubID + site.Publisher = &publisher + } else { + site.Publisher = &openrtb.Publisher{ID: pubID} + } + request.Site = &site + } else if request.App != nil { + app := *request.App + if app.Publisher != nil { + publisher := *app.Publisher + publisher.ID = pubID + app.Publisher = &publisher + } else { + app.Publisher = &openrtb.Publisher{ID: pubID} + } + request.App = &app + } + + request.Imp = imps + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + // passback + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + // bad requests + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + // error + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + // parse response + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + // map imps by id + impsByID := make(map[string]openrtb.Imp) + for i := 0; i < len(internalRequest.Imp); i++ { + impsByID[internalRequest.Imp[i].ID] = internalRequest.Imp[i] + } + + var errs []error + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + imp := impsByID[bid.ImpID] + bidType := getBidType(imp) + if &imp != nil && bidType != "" { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + } + return bidResponse, errs +} + +func getBidType(imp openrtb.Imp) openrtb_ext.BidType { + // derive the bidtype purely from the impression itself + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative + } + return "" +} + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + +func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { + a := adapters.NewHTTPAdapter(config) + + return &PulsePointAdapter{ + http: a, + URI: uri, + } +} + // used for cookies and such func (a *PulsePointAdapter) Name() string { return "pulsepoint" } -func (a *PulsePointAdapter) SkipNoCookies() bool { - return false -} - // parameters for pulsepoint adapter. type PulsepointParams struct { PublisherId int `json:"cp"` @@ -39,6 +195,10 @@ type PulsepointParams struct { AdSize string `json:"cf"` } +func (a *PulsePointAdapter) SkipNoCookies() bool { + return false +} + func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) @@ -195,11 +355,6 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde return bids, nil } -func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PulsePointAdapter{ - http: a, - URI: uri, - } -} +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index 3eede431a12..33023d0500a 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,26 +1,43 @@ package pulsepoint import ( + "encoding/json" + "net/http" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" + "bytes" "context" - "encoding/json" "fmt" "io/ioutil" - "net/http" "net/http/httptest" - "testing" "time" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" ) +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderPulsepoint, config.Adapter{ + Endpoint: "http://bidder.pulsepoint.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) +} + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + /** * Verify adapter names are setup correctly. */ @@ -75,7 +92,6 @@ func TestPulsePointOpenRTBRequest(t *testing.T) { bidder := req.Bidders[0] adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) adapter.Call(ctx, req, bidder) - fmt.Println(service.LastBidRequest) adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) @@ -290,3 +306,7 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service.LastBidRequest = &lastBidRequest return service } + +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json new file mode 100644 index 00000000000..e99ca648572 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json new file mode 100644 index 00000000000..d4cf797d219 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json new file mode 100644 index 00000000000..6e16f997661 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app" + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "1234" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json new file mode 100644 index 00000000000..6d658a2423a --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html" + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json new file mode 100644 index 00000000000..1a10355344c --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }, { + "id": "video-1", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }, { + "id": "video-1", + "tagid": "2001", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, { + "id": "video-1-bid", + "impid": "video-1", + "price": 4.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "201" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }, { + "bid": { + "id": "video-1-bid", + "impid": "video-1", + "price": 4.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "201" + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/native.json b/adapters/pulsepoint/pulsepointtest/exemplary/native.json new file mode 100644 index 00000000000..72c8532d783 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/native.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "native-1", + "native": { + "ver": "1.0", + "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}" + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "native-1", + "tagid": "2001", + "native": { + "ver": "1.0", + "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}" + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "native-1-bid", + "impid": "native-1", + "price": 3.5, + "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}", + "adomain": [ + "advertiser.com" + ], + "crid": "20" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "native-1-bid", + "impid": "native-1", + "price": 3.5, + "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}", + "adomain": [ + "advertiser.com" + ], + "crid": "20" + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/video.json b/adapters/pulsepoint/pulsepointtest/exemplary/video.json new file mode 100644 index 00000000000..980f49f2d14 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/video.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "video-1", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "video-1", + "tagid": "2001", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "video-1-bid", + "impid": "video-1", + "price": 3.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "video-1-bid", + "impid": "video-1", + "price": 3.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/params/race/banner.json b/adapters/pulsepoint/pulsepointtest/params/race/banner.json index 1f2e34df47c..bc3e9371a5f 100644 --- a/adapters/pulsepoint/pulsepointtest/params/race/banner.json +++ b/adapters/pulsepoint/pulsepointtest/params/race/banner.json @@ -1,5 +1,4 @@ { - "cf": "300X250", "cp": 512379, "ct": 486653 } diff --git a/adapters/pulsepoint/pulsepointtest/params/race/native.json b/adapters/pulsepoint/pulsepointtest/params/race/native.json new file mode 100644 index 00000000000..57867bc27c0 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "cp": 512379, + "ct": 486655 +} diff --git a/adapters/pulsepoint/pulsepointtest/params/race/video.json b/adapters/pulsepoint/pulsepointtest/params/race/video.json new file mode 100644 index 00000000000..d8ef37eb793 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "cp": 512379, + "ct": 486654 +} diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json new file mode 100644 index 00000000000..b5209ed4bbe --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-2", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": "300", + "h": "250" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type uint64", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json new file mode 100644 index 00000000000..3fe1422bb00 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Bad user input: HTTP status 400", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json new file mode 100644 index 00000000000..2d0c74d39d7 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": "1234", + "ct": "1001" + } + } + }] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [{ + "value": "json: cannot unmarshal string into Go struct field ExtImpPulsePoint.cp of type int", + "comparison": "literal" + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json new file mode 100644 index 00000000000..7de9bd3c264 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": "" + }] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [{ + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/error.json b/adapters/pulsepoint/pulsepointtest/supplemental/error.json new file mode 100644 index 00000000000..c469e3bc2f9 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/error.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Bad server response: HTTP status 503", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json new file mode 100644 index 00000000000..30b57e1ef60 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-2", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/passback.json b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json new file mode 100644 index 00000000000..434f5a7a141 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 31342a4c5ff..8da57d6ec59 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -65,6 +65,7 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" + "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/revcontent" "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" @@ -166,6 +167,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, + openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 6e12ed00536..4d92fe9fe9c 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -8,7 +8,6 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/lifestreet" - "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -58,7 +57,7 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.Bidder } // Ignore Legacy Bidders - if bidderName == openrtb_ext.BidderLifestreet || bidderName == openrtb_ext.BidderPulsepoint { + if bidderName == openrtb_ext.BidderLifestreet { continue } @@ -99,12 +98,6 @@ func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos a bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter) } - // Pulsepoint - if infos[string(openrtb_ext.BidderPulsepoint)].Status == adapters.StatusActive { - adapter := pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderPulsepoint)].Endpoint) - bidders[openrtb_ext.BidderPulsepoint] = adaptLegacyAdapter(adapter) - } - return bidders } diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index d7059648877..20402e1e68c 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -10,7 +10,6 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/lifestreet" - "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -214,9 +213,9 @@ func TestBuildBidders(t *testing.T) { }, { description: "Success - Ignores Legacy", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}, "pulsepoint": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive, "pulsepoint": infoActive}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder, openrtb_ext.BidderPulsepoint: inconsequentialBuilder}, + adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}}, + bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), }, @@ -267,7 +266,6 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) { cfg := config.Adapter{Endpoint: "anyEndpoint"} expectedLifestreet := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} - expectedPulsepoint := &adaptedAdapter{pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} testCases := []struct { description string @@ -276,29 +274,23 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) { expected map[openrtb_ext.BidderName]adaptedBidder }{ { - description: "All Active", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoActive}, - expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet, "pulsepoint": expectedPulsepoint}, + description: "Active", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive}, + expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, }, { - description: "All Disabled", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoDisabled, "pulsepoint": infoDisabled}, + description: "Disabled", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoDisabled}, expected: map[openrtb_ext.BidderName]adaptedBidder{}, }, { - description: "All Unknown", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoUnknown, "pulsepoint": infoUnknown}, + description: "Unknown", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoUnknown}, expected: map[openrtb_ext.BidderName]adaptedBidder{}, }, - { - description: "Mixed", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoUnknown}, - expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, - }, } for _, test := range testCases { diff --git a/openrtb_ext/imp_pulsepoint.go b/openrtb_ext/imp_pulsepoint.go new file mode 100644 index 00000000000..c168c80f1bb --- /dev/null +++ b/openrtb_ext/imp_pulsepoint.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.pulsepoint +// PubId/TagId are mandatory params + +type ExtImpPulsePoint struct { + PubID int `json:"cp"` + TagID int `json:"ct"` +} diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 716e453000e..056a0bf3d7c 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -4,6 +4,12 @@ capabilities: app: mediaTypes: - banner + - video + - audio + - native site: mediaTypes: - banner + - video + - audio + - native diff --git a/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json index c4704c3b42e..673fd2efd6f 100644 --- a/static/bidder-params/pulsepoint.json +++ b/static/bidder-params/pulsepoint.json @@ -11,12 +11,7 @@ "ct": { "type": "integer", "description": "An ID which identifies the ad slot being sold" - }, - "cf": { - "type": "string", - "pattern": "^[0-9]+[xX][0-9]+$", - "description": "The size of the ad slot being sold. This should be a string like 300X250" } }, - "required": ["cp", "ct", "cf"] + "required": ["cp", "ct"] } From 01645db6207f4defe327767b50fcfe6a725ca340 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 28 Jan 2021 12:27:19 -0800 Subject: [PATCH 311/603] Debug disable feature implementation: (#1677) Co-authored-by: Veronika Solovei --- adapters/info.go | 5 + config/accounts.go | 1 + config/config.go | 1 + exchange/adapter_util.go | 7 +- exchange/adapter_util_test.go | 8 +- exchange/bidder.go | 25 +- exchange/bidder_test.go | 177 ++++++++----- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +- exchange/exchange.go | 58 +++-- exchange/exchange_test.go | 232 +++++++++++++++--- .../exchangetest/append-bidder-names.json | 3 - exchange/exchangetest/debuglog_disabled.json | 3 - exchange/exchangetest/debuglog_enabled.json | 3 - exchange/legacy.go | 2 +- exchange/legacy_test.go | 10 +- exchange/targeting_test.go | 5 +- 17 files changed, 393 insertions(+), 161 deletions(-) diff --git a/adapters/info.go b/adapters/info.go index 7f3ad9c3af0..19897ac7031 100644 --- a/adapters/info.go +++ b/adapters/info.go @@ -206,6 +206,11 @@ type BidderInfo struct { Capabilities *CapabilitiesInfo `yaml:"capabilities" json:"capabilities"` AliasOf string `json:"aliasOf,omitempty"` ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed" json:"-" xml:"-"` + Debug *DebugInfo `yaml:"debug,omitempty" json:"-" xml:"-"` +} + +type DebugInfo struct { + Allow bool `yaml:"allow" json:"allow"` } type MaintainerInfo struct { diff --git a/config/accounts.go b/config/accounts.go index 0faf5f5f529..548092451c3 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -19,6 +19,7 @@ type Account struct { EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` + DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` } // AccountCCPA represents account-specific CCPA configuration diff --git a/config/config.go b/config/config.go index 2a99e278deb..ea5de90d28a 100755 --- a/config/config.go +++ b/config/config.go @@ -937,6 +937,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("blacklisted_accts", []string{""}) v.SetDefault("account_required", false) v.SetDefault("account_defaults.disabled", false) + v.SetDefault("account_defaults.debug_allow", true) v.SetDefault("certificates_file", "") v.SetDefault("auto_gen_source_tid", true) diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 4d92fe9fe9c..7e271376868 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -38,7 +38,12 @@ func buildExchangeBidders(cfg *config.Configuration, infos adapters.BidderInfos, exchangeBidders := make(map[openrtb_ext.BidderName]adaptedBidder, len(bidders)) for bidderName, bidder := range bidders { - exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName) + info, infoFound := infos[string(bidderName)] + if !infoFound { + errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder)) + continue + } + exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName, info.Debug) } return exchangeBidders, nil diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 20402e1e68c..92eb01291fb 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -40,7 +40,7 @@ func TestBuildAdaptersSuccess(t *testing.T) { appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive) - appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus) + appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) appnexusBidderValidated := addValidatedBidderMiddleware(appnexusBidderAdapted) idLegacyAdapted := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} @@ -77,11 +77,11 @@ func TestBuildExchangeBidders(t *testing.T) { appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive) - appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus) + appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{}) rubiconBidderWithInfo := adapters.EnforceBidderInfo(rubiconBidder, infoActive) - rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon) + rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil) testCases := []struct { description string @@ -397,7 +397,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { type fakeAdaptedBidder struct{} -func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { +func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return nil, nil } diff --git a/exchange/bidder.go b/exchange/bidder.go index 6e84c260f03..5b5852bdd62 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -49,7 +49,7 @@ 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, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -93,7 +93,7 @@ type pbsOrtbSeatBid struct { // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) -func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName) adaptedBidder { +func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *adapters.DebugInfo) adaptedBidder { return &bidderAdapter{ Bidder: bidder, BidderName: name, @@ -102,10 +102,18 @@ func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Config config: bidderAdapterConfig{ Debug: cfg.Debug, DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics, + DebugInfo: adapters.DebugInfo{Allow: parseDebugInfo(debugInfo)}, }, } } +func parseDebugInfo(info *adapters.DebugInfo) bool { + if info == nil { + return true + } + return info.Allow +} + type bidderAdapter struct { Bidder adapters.Bidder BidderName openrtb_ext.BidderName @@ -117,9 +125,10 @@ type bidderAdapter struct { type bidderAdapterConfig struct { Debug config.Debug DisableConnMetrics bool + DebugInfo adapters.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -155,8 +164,14 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi for i := 0; i < len(reqData); i++ { httpInfo := <-responseChannel // If this is a test bid, capture debugging info from the requests. - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + // Write debug data to ext in case if: + // - debugContextKey (url param) in true + // - account debug is allowed + // - bidder debug is allowed + if accountDebugAllowed && bidder.config.DebugInfo.Allow { + if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } } if httpInfo.err == nil { diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 93aaf241a01..b809c7e0f51 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -35,6 +35,16 @@ import ( // 1. The Bidder implementation is called with the arguments we expect. // 2. The returned values are correct for a non-test bid. func TestSingleBidder(t *testing.T) { + type aTest struct { + debugInfo *adapters.DebugInfo + httpCallsLen int + } + + testCases := []*aTest{ + {&adapters.DebugInfo{Allow: false}, 0}, + {&adapters.DebugInfo{Allow: true}, 1}, + } + respStatus := 200 respBody := "{\"bid\":false}" server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) @@ -46,24 +56,6 @@ func TestSingleBidder(t *testing.T) { bidAdjustment := 2.0 firstInitialPrice := 3.0 secondInitialPrice := 4.0 - mockBidderResponse := &adapters.BidderResponse{ - Bids: []*adapters.TypedBid{ - { - Bid: &openrtb.Bid{ - Price: firstInitialPrice, - }, - BidType: openrtb_ext.BidTypeBanner, - DealPriority: 4, - }, - { - Bid: &openrtb.Bid{ - Price: secondInitialPrice, - }, - BidType: openrtb_ext.BidTypeVideo, - DealPriority: 5, - }, - }, - } bidderImpl := &goodSingleBidder{ httpRequest: &adapters.RequestData{ @@ -72,53 +64,81 @@ func TestSingleBidder(t *testing.T) { Body: []byte("{\"key\":\"val\"}"), Headers: http.Header{}, }, - bidResponse: mockBidderResponse, + bidResponse: nil, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) - // Make sure the goodSingleBidder was called with the expected arguments. - if bidderImpl.httpResponse == nil { - t.Errorf("The Bidder should be called with the server's response.") - } - if bidderImpl.httpResponse.StatusCode != respStatus { - t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode) - } - if string(bidderImpl.httpResponse.Body) != respBody { - t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body)) - } + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) - // Make sure the returned values are what we expect - if len(errs) != 0 { - t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) - } - if len(seatBid.bids) != len(mockBidderResponse.Bids) { - t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) - } - for index, typedBid := range mockBidderResponse.Bids { - if typedBid.Bid != seatBid.bids[index].bid { - t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index) + for _, test := range testCases { + + mockBidderResponse := &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb.Bid{ + Price: firstInitialPrice, + }, + BidType: openrtb_ext.BidTypeBanner, + DealPriority: 4, + }, + { + Bid: &openrtb.Bid{ + Price: secondInitialPrice, + }, + BidType: openrtb_ext.BidTypeVideo, + DealPriority: 5, + }, + }, } - if typedBid.BidType != seatBid.bids[index].bidType { - t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + bidderImpl.bidResponse = mockBidderResponse + + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + seatBid, errs := bidder.requestBid(ctx, &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + + // Make sure the goodSingleBidder was called with the expected arguments. + if bidderImpl.httpResponse == nil { + t.Errorf("The Bidder should be called with the server's response.") } - if typedBid.DealPriority != seatBid.bids[index].dealPriority { - t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + if bidderImpl.httpResponse.StatusCode != respStatus { + t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode) + } + if string(bidderImpl.httpResponse.Body) != respBody { + t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body)) } - } - if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice { - t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price) - } - if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice { - t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price) - } - if len(seatBid.httpCalls) != 0 { - t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) - } - if len(seatBid.ext) != 0 { - t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext)) + // Make sure the returned values are what we expect + if len(errs) != 0 { + t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) + } + if len(seatBid.bids) != len(mockBidderResponse.Bids) { + t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) + } + for index, typedBid := range mockBidderResponse.Bids { + if typedBid.Bid != seatBid.bids[index].bid { + t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index) + } + if typedBid.BidType != seatBid.bids[index].bidType { + t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + } + if typedBid.DealPriority != seatBid.bids[index].dealPriority { + t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType) + } + } + if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice { + t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price) + } + if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice { + t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price) + } + if len(seatBid.httpCalls) != test.httpCallsLen { + t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) + } + + if len(seatBid.ext) != 0 { + t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext)) + } } } @@ -162,9 +182,9 @@ func TestMultiBidder(t *testing.T) { }}, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -524,7 +544,7 @@ func TestMultiCurrencies(t *testing.T) { ) // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -540,6 +560,7 @@ func TestMultiCurrencies(t *testing.T) { 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, + true, ) // Verify: @@ -675,7 +696,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid( context.Background(), @@ -684,6 +705,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, + true, ) // Verify: @@ -841,7 +863,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -856,6 +878,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, + true, ) // Verify: @@ -1031,7 +1054,7 @@ func TestMobileNativeTypes(t *testing.T) { }, bidResponse: tc.mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBids, _ := bidder.requestBid( @@ -1041,6 +1064,7 @@ func TestMobileNativeTypes(t *testing.T) { 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, + true, ) var actualValue string @@ -1052,9 +1076,9 @@ func TestMobileNativeTypes(t *testing.T) { } func TestErrorReporting(t *testing.T) { - bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1235,9 +1259,9 @@ func TestCallRecordAdapterConnections(t *testing.T) { metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once() // Run requestBid using an http.Client with a mock handler - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -1412,6 +1436,23 @@ func TestTimeoutNotificationOn(t *testing.T) { assert.EqualValues(t, logExpected, logActual) } +func TestParseDebugInfoTrue(t *testing.T) { + debugInfo := &adapters.DebugInfo{Allow: true} + resDebugInfo := parseDebugInfo(debugInfo) + assert.True(t, resDebugInfo, "Debug Allow value should be true") +} + +func TestParseDebugInfoFalse(t *testing.T) { + debugInfo := &adapters.DebugInfo{Allow: false} + resDebugInfo := parseDebugInfo(debugInfo) + assert.False(t, resDebugInfo, "Debug Allow value should be false") +} + +func TestParseDebugInfoIsNil(t *testing.T) { + resDebugInfo := parseDebugInfo(nil) + assert.True(t, resDebugInfo, "Debug Allow value should be true") +} + func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { bidderInfo := adapters.BidderInfo{ Status: adapters.StatusActive, diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 3700bac4fb3..cf74dfb1dd5 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -28,8 +28,8 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo) +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { + seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) if validationErrors := removeInvalidBids(request, 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 bf35b2db59c..e7fc0b046dd 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 3) assert.Len(t, errs, 0) } @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 0) assert.Len(t, errs, 5) } @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 2) assert.Len(t, errs, 3) } @@ -246,7 +246,7 @@ func TestCurrencyBids(t *testing.T) { Cur: tc.brqCur, } - seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, expectedValidBids) assert.Len(t, errs, expectedErrs) } @@ -257,6 +257,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index 99ac8a8884a..cfb9c6d39da 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,7 +14,6 @@ import ( "strings" "time" - uuid "github.com/gofrs/uuid" "github.com/prebid/prebid-server/stored_requests" "github.com/golang/glog" @@ -135,7 +134,15 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * _, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() } + if debugLog == nil { + debugLog = &DebugLog{Enabled: false} + } + debugInfo := getDebugInfo(r.BidRequest, requestExt) + + debugInfo = debugInfo && r.Account.DebugAllow + debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow + if debugInfo { ctx = e.makeDebugContext(ctx, debugInfo) } @@ -163,10 +170,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * // Get currency rates conversions for the auction conversions := e.currencyConverter.Rates() - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow) var auc *auction var cacheErrs []error + var bidResponseExt *openrtb_ext.ExtBidResponse if anyBidsReturned { var bidCategory map[string]string @@ -194,34 +202,35 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * dealErrs := applyDealSupport(r.BidRequest, auc, bidCategory) errs = append(errs, dealErrs...) } - cacheErrs := auc.doCache(ctx, e.cache, targData, evTracking, r.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog) + + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + if debugLog.Enabled { + if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + debugLog.Data.Response = string(bidRespExtBytes) + } else { + debugLog.Data.Response = "Unable to marshal response ext for debugging" + errs = append(errs, err) + } + } + + cacheErrs = auc.doCache(ctx, e.cache, targData, evTracking, r.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog) if len(cacheErrs) > 0 { errs = append(errs, cacheErrs...) } + targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory) } - } + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + } else { + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + if debugLog.Enabled { - // Ensure caching errors are added in case auc.doCache was called and errors were returned - if len(cacheErrs) > 0 { - bidderCacheErrs := errsToBidderErrors(cacheErrs) - bidResponseExt.Errors[openrtb_ext.PrebidExtKey] = append(bidResponseExt.Errors[openrtb_ext.PrebidExtKey], bidderCacheErrs...) - } - - if debugLog != nil && debugLog.Enabled { - if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { - debugLog.Data.Response = string(bidRespExtBytes) - } else { - debugLog.Data.Response = "Unable to marshal response ext for debugging" - errs = append(errs, err) - } - if !anyBidsReturned { - if rawUUID, err := uuid.NewV4(); err == nil { - debugLog.CacheKey = rawUUID.String() + if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + debugLog.Data.Response = string(bidRespExtBytes) } else { + debugLog.Data.Response = "Unable to marshal response ext for debugging" errs = append(errs, err) } } @@ -342,7 +351,8 @@ func (e *exchange) getAllBids( ctx context.Context, bidderRequests []BidderRequest, bidAdjustments map[string]float64, - conversions currency.Conversions) ( + conversions currency.Conversions, + accountDebugAllowed bool) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -373,7 +383,7 @@ func (e *exchange) getAllBids( } var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo) + bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) // Add in time reporting elapsed := time.Since(start) @@ -758,7 +768,7 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb for bidderName, responseExtra := range adapterExtra { - if debugInfo { + if debugInfo && len(responseExtra.HttpCalls) > 0 { bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } // Only make an entry for bidder errors if the bidder reported any. diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 416875a181c..8bdf6c451e0 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -165,41 +165,66 @@ func TestDebugBehaviour(t *testing.T) { type outTest struct { debugInfoIncluded bool } + + type debugData struct { + bidderLevelDebugAllowed bool + accountLevelDebugAllowed bool + } + type aTest struct { - desc string - in inTest - out outTest + desc string + in inTest + out outTest + debugData debugData } testCases := []aTest{ { - desc: "test flag equals zero, ext debug flag false, no debug info expected", - in: inTest{test: 0, debug: false}, - out: outTest{debugInfoIncluded: false}, + desc: "test flag equals zero, ext debug flag false, no debug info expected", + in: inTest{test: 0, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, }, { - desc: "test flag equals zero, ext debug flag true, debug info expected", - in: inTest{test: 0, debug: true}, - out: outTest{debugInfoIncluded: true}, + desc: "test flag equals zero, ext debug flag true, debug info expected", + in: inTest{test: 0, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, }, { - desc: "test flag equals 1, ext debug flag false, debug info expected", - in: inTest{test: 1, debug: false}, - out: outTest{debugInfoIncluded: true}, + desc: "test flag equals 1, ext debug flag false, debug info expected", + in: inTest{test: 1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, }, { - desc: "test flag equals 1, ext debug flag true, debug info expected", - in: inTest{test: 1, debug: true}, - out: outTest{debugInfoIncluded: true}, + desc: "test flag equals 1, ext debug flag true, debug info expected", + in: inTest{test: 1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", - in: inTest{test: 2, debug: false}, - out: outTest{debugInfoIncluded: false}, + desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", + in: inTest{test: 2, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: true}, + desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + }, + { + desc: "test account level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, false}, + }, + { + desc: "test bidder level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{false, true}, }, } @@ -239,17 +264,25 @@ func TestDebugBehaviour(t *testing.T) { } e := new(exchange) - e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus), - } + e.cache = &wellBehavedCache{} e.me = &metricsConf.DummyMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher + ctx := context.Background() + // Run tests for _, test := range testCases { + + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), + } + + //request level debug key + ctx = context.WithValue(ctx, DebugContextKey, test.in.debug) + bidRequest.Test = test.in.test if test.in.debug { @@ -260,13 +293,13 @@ func TestDebugBehaviour(t *testing.T) { auctionRequest := AuctionRequest{ BidRequest: bidRequest, - Account: config.Account{}, + Account: config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed}, UserSyncs: &emptyUsersync{}, StartTime: time.Now(), } // Run test - outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil) + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) @@ -292,8 +325,132 @@ func TestDebugBehaviour(t *testing.T) { if test.in.debug { diffJson(t, test.desc, bidRequest.Ext, actualExt.Debug.ResolvedRequest.Ext) } + } else if !test.debugData.bidderLevelDebugAllowed && test.debugData.accountLevelDebugAllowed { + assert.Equal(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") + + } else { + assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") + } + } +} + +func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { + + type testCase struct { + bidder1DebugEnabled bool + bidder2DebugEnabled bool + } + + testCases := []testCase{ + { + bidder1DebugEnabled: true, bidder2DebugEnabled: true, + }, + { + bidder1DebugEnabled: true, bidder2DebugEnabled: false, + }, + { + bidder1DebugEnabled: false, bidder2DebugEnabled: true, + }, + { + bidder1DebugEnabled: false, bidder2DebugEnabled: false, + }, + } + + // Set up test + noBidServer := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + categoriesFetcher, err := newCategoryFetcher("./test/category-mapping") + if err != nil { + t.Errorf("Failed to create a category Fetcher: %v", err) + } + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{}, + } + + e := new(exchange) + e.cache = &wellBehavedCache{} + e.me = &metricsConf.DummyMetricsEngine{} + e.gDPR = gdpr.AlwaysAllow{} + e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + e.categoriesFetcher = categoriesFetcher + + debugLog := DebugLog{} + + for _, testCase := range testCases { + bidRequest := &openrtb.BidRequest{ + ID: "some-request-id", + Imp: []openrtb.Imp{{ + ID: "some-impression-id", + Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}`), + }}, + Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + AT: 1, + TMax: 500, + } + + bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`) + + auctionRequest := AuctionRequest{ + BidRequest: bidRequest, + Account: config.Account{DebugAllow: true}, + UserSyncs: &emptyUsersync{}, + StartTime: time.Now(), + } + + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder1DebugEnabled}), + openrtb_ext.BidderTelaria: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder2DebugEnabled}), + } + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) + // Assert no HoldAuction err + assert.NoErrorf(t, err, "ex.HoldAuction returned an err") + assert.NotNilf(t, outBidResponse.Ext, "outBidResponse.Ext should not be nil") + + actualExt := &openrtb_ext.ExtBidResponse{} + err = json.Unmarshal(outBidResponse.Ext, actualExt) + assert.NoErrorf(t, err, "JSON field unmarshaling err. ") + + assert.NotEmpty(t, actualExt.Prebid, "ext.prebid should not be empty") + assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set") + assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp has incorrect value") + + assert.NotNilf(t, actualExt, "ext.debug field is expected to be included in this outBidResponse.Ext and not be nil") + + // Assert "Debug fields + if testCase.bidder1DebugEnabled { + assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["appnexus"][0].Uri, "Url for bidder with debug enabled is incorrect") + assert.NotNilf(t, actualExt.Debug.HttpCalls["appnexus"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil") + } + if testCase.bidder2DebugEnabled { + assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["telaria"][0].Uri, "Url for bidder with debug enabled is incorrect") + assert.NotNilf(t, actualExt.Debug.HttpCalls["telaria"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil") + } + if !testCase.bidder1DebugEnabled { + assert.Nil(t, actualExt.Debug.HttpCalls["appnexus"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil") + } + if !testCase.bidder2DebugEnabled { + assert.Nil(t, actualExt.Debug.HttpCalls["telaria"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil") + } + + if testCase.bidder1DebugEnabled && testCase.bidder2DebugEnabled { + assert.Equal(t, 2, len(actualExt.Debug.HttpCalls), "With bidder level debug enable option for both bidders http calls should have 2 elements") } } + } func TestReturnCreativeEndToEnd(t *testing.T) { @@ -434,7 +591,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { e := new(exchange) e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), } e.cache = &wellBehavedCache{} e.me = &metricsConf.DummyMetricsEngine{} @@ -465,7 +622,8 @@ func TestReturnCreativeEndToEnd(t *testing.T) { } // Run test - outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil) + debugLog := DebugLog{} + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) // Assert return error, if any if testGroup.expectError { @@ -716,7 +874,7 @@ func TestBidReturnsCreative(t *testing.T) { } e := new(exchange) e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), } e.cache = &wellBehavedCache{} e.me = &metricsConf.DummyMetricsEngine{} @@ -1059,8 +1217,9 @@ func TestRaceIntegration(t *testing.T) { UserSyncs: &emptyUsersync{}, } + debugLog := DebugLog{} ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) - _, err := ex.HoldAuction(context.Background(), auctionRequest, nil) + _, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -1279,8 +1438,8 @@ func TestPanicRecoveryHighLevel(t *testing.T) { Account: config.Account{}, UserSyncs: &emptyUsersync{}, } - - _, err := e.HoldAuction(context.Background(), auctionRequest, nil) + debugLog := DebugLog{} + _, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -1406,14 +1565,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Account: config.Account{ ID: "testaccount", EventsEnabled: spec.EventsEnabled, + DebugAllow: true, }, UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs), } if spec.StartTime > 0 { auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6) } + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) - bid, err := ex.HoldAuction(context.Background(), auctionRequest, debugLog) + bid, err := ex.HoldAuction(ctx, auctionRequest, debugLog) responseTimes := extractResponseTimes(t, filename, bid) for _, bidderName := range biddersInAuction { if _, ok := responseTimes[bidderName]; !ok { @@ -2622,7 +2784,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -2801,7 +2963,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index 1247b9f0261..507aee1a103 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -166,9 +166,6 @@ }, "ext": { "debug": { - "httpcalls": { - "appnexus": null - }, "resolvedrequest": { "id": "some-request-id", "imp": [ diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index 3dd04f666fa..e95b556b7ec 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -175,9 +175,6 @@ }, "ext": { "debug": { - "httpcalls": { - "appnexus": null - }, "resolvedrequest": { "id": "some-request-id", "imp": [ diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index a70013b4ac7..851bda69097 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -175,9 +175,6 @@ }, "ext": { "debug": { - "httpcalls": { - "appnexus": null - }, "resolvedrequest": { "id": "some-request-id", "imp": [ diff --git a/exchange/legacy.go b/exchange/legacy.go index dc81e7a1182..b4845b76c69 100644 --- a/exchange/legacy.go +++ b/exchange/legacy.go @@ -33,7 +33,7 @@ type adaptedAdapter struct { // // This is not ideal. OpenRTB provides a superset of the legacy data structures. // For requests which use those features, the best we can do is respond with "no bid". -func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) { +func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name) if legacyRequest == nil || legacyBidder == nil { return nil, errs diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go index fcc8b09fdc8..2cef4feae40 100644 --- a/exchange/legacy_test.go +++ b/exchange/legacy_test.go @@ -61,7 +61,7 @@ func TestSiteVideo(t *testing.T) { exchangeBidder := adaptLegacyAdapter(&mockAdapter) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if len(errs) > 0 { t.Errorf("Unexpected error requesting bids: %v", errs) } @@ -95,7 +95,7 @@ func TestAppBanner(t *testing.T) { exchangeBidder := adaptLegacyAdapter(&mockAdapter) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if len(errs) > 0 { t.Errorf("Unexpected error requesting bids: %v", errs) } @@ -141,7 +141,7 @@ func TestBidTransforms(t *testing.T) { exchangeBidder := adaptLegacyAdapter(&mockAdapter) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if len(errs) != 1 { t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) } @@ -290,7 +290,7 @@ func TestErrorResponse(t *testing.T) { exchangeBidder := adaptLegacyAdapter(&mockAdapter) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if len(errs) != 1 { t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) } @@ -329,7 +329,7 @@ func TestWithTargeting(t *testing.T) { } exchangeBidder := adaptLegacyAdapter(&mockAdapter) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}) + bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if len(errs) != 0 { t.Fatalf("This should not produce errors. Got %v", errs) } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 160d7465ff2..2a77c4f7517 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -115,7 +115,8 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op UserSyncs: &emptyUsersync{}, } - bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, nil) + debugLog := DebugLog{} + bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Fatalf("Unexpected errors running auction: %v", err) @@ -141,7 +142,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ mockServerURL: mockServerURL, bids: bids, - }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus) + }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) } return adapterMap } From ef06fac6494f7e91f9d1d90ab07f02f8499b5efc Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 28 Jan 2021 15:28:17 -0500 Subject: [PATCH 312/603] Always use fallback GVL for TCF1 (#1657) * Update TCF2 GVL subdomain and always use fallback GVL for TCF1 * Add config test coverage for invalid TCF1 FetchGVL and AMP Exception * Delete obselete test --- config/config.go | 4 +- config/config_test.go | 46 ++++- gdpr/vendorlist-fetching.go | 19 +- gdpr/vendorlist-fetching_test.go | 339 ++----------------------------- 4 files changed, 60 insertions(+), 348 deletions(-) diff --git a/config/config.go b/config/config.go index ea5de90d28a..f040040bf64 100755 --- a/config/config.go +++ b/config/config.go @@ -217,7 +217,7 @@ func (cfg *GDPR) validate(errs []error) []error { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } if cfg.TCF1.FetchGVL == true { - glog.Warning("gdpr.tcf1.fetch_gvl is deprecated and will be removed in a future version, at which point TCF1 will always use the fallback GVL") + errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")) } return errs } @@ -910,7 +910,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) - v.SetDefault("gdpr.tcf1.fetch_gvl", true) + v.SetDefault("gdpr.tcf1.fetch_gvl", false) v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json") v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) diff --git a/config/config_test.go b/config/config_test.go index 57631395fb8..7ff5f0fafa1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -527,12 +527,6 @@ func TestNegativeRequestSize(t *testing.T) { assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1") } -func TestNegativeVendorID(t *testing.T) { - cfg := newDefaultConfig(t) - cfg.GDPR.HostVendorID = -1 - assertOneError(t, cfg.validate(), "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1") -} - func TestNegativePrometheusTimeout(t *testing.T) { cfg := newDefaultConfig(t) cfg.Metrics.Prometheus.Port = 8001 @@ -540,10 +534,44 @@ func TestNegativePrometheusTimeout(t *testing.T) { assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") } -func TestOverflowedVendorID(t *testing.T) { +func TestInvalidHostVendorID(t *testing.T) { + tests := []struct { + description string + vendorID int + wantErrorMsg string + }{ + { + description: "Negative GDPR.HostVendorID", + vendorID: -1, + wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1", + }, + { + description: "Overflowed GDPR.HostVendorID", + vendorID: (0xffff) + 1, + wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536", + }, + } + + for _, tt := range tests { + cfg := newDefaultConfig(t) + cfg.GDPR.HostVendorID = tt.vendorID + errs := cfg.validate() + + assert.Equal(t, 1, len(errs), tt.description) + assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) + } +} + +func TestInvalidFetchGVL(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.GDPR.TCF1.FetchGVL = true + assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward") +} + +func TestInvalidAMPException(t *testing.T) { cfg := newDefaultConfig(t) - cfg.GDPR.HostVendorID = (0xffff) + 1 - assertOneError(t, cfg.validate(), "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536") + cfg.GDPR.AMPException = true + assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") } func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index de6da7b4bfb..95a17109c90 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -28,22 +28,21 @@ type saveVendors func(uint16, api.VendorList) func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, tcfSpecVersion uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { var fallback api.VendorList - if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) > 0 { - fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) - } - // If we are not going to try fetching the GVL dynamically, we have a simple fetcher. - if !cfg.TCF1.FetchGVL && tcfSpecVersion == tcf1SpecVersion { - if fallback != nil { - return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { - return fallback, nil - } - } + if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) == 0 { return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { return nil, makeVendorListNotFoundError(vendorListVersion) } } + if tcfSpecVersion == tcf1SpecVersion { + fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) + + return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { + return fallback, nil + } + } + cacheSave, cacheLoad := newVendorListCache(fallback) preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 62c6a5f9d09..77f3f29463c 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -28,63 +28,24 @@ func TestTCF1FetcherInitialLoad(t *testing.T) { testCases := []test{ { - description: "Fetch - No Fallback - Vendor List 1", + description: "Fallback - Vendor List 1", setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "Fetch - Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "Fetch - Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fetch - Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: true, vendorListVersion: 1, }, expected: vendorListFallbackExpected, }, { - description: "No Fetch - Fallback - Vendor List 2", + description: "Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: true, vendorListVersion: 2, }, expected: vendorListFallbackExpected, }, { - description: "No Fetch - No Fallback - Vendor List 1", + description: "No Fallback - Vendor List 1", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 1, }, @@ -93,9 +54,8 @@ func TestTCF1FetcherInitialLoad(t *testing.T) { }, }, { - description: "No Fetch - No Fallback - Vendor List 2", + description: "No Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 2, }, @@ -125,72 +85,32 @@ func TestTCF2FetcherInitialLoad(t *testing.T) { testCases := []test{ { - description: "Fetch - No Fallback - Vendor List 1", + description: "Fallback - Vendor List 1", setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "Fetch - Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "Fetch - Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fetch - Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: true, vendorListVersion: 1, }, expected: vendorList1Expected, }, { - description: "No Fetch - Fallback - Vendor List 2", + description: "Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: true, vendorListVersion: 2, }, expected: vendorList2Expected, }, { - description: "No Fetch - No Fallback - Vendor List 1", + description: "No Fallback - Vendor List 1", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 1, }, expected: vendorList1Expected, }, { - description: "No Fetch - No Fallback - Vendor List 2", + description: "No Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 2, }, @@ -203,65 +123,6 @@ func TestTCF2FetcherInitialLoad(t *testing.T) { } } -func TestTCF1FetcherDynamicLoadListExists(t *testing.T) { - // Loads the first vendor list during initialization by setting the latest vendor list version to 1. - // All other vendor lists will be dynamically loaded. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 1, - vendorLists: map[int]string{ - 1: tcf1VendorList1, - 2: tcf1VendorList2, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "Fetch - Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fetch - Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: false, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: false, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - } - - for _, test := range testCases { - runTest(t, test, tcf1SpecVersion, server) - } -} - func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor lists will be dynamically loaded. @@ -278,36 +139,16 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { testCases := []test{ { - description: "Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "Fetch - Fallback - Vendor List 2", + description: "Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: true, enableTCF1Fallback: true, vendorListVersion: 2, }, expected: vendorList2Expected, }, { - description: "No Fetch - Fallback - Vendor List 2", + description: "No Fallback - Vendor List 2", setup: testSetup{ - enableTCF1Fetch: false, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fetch - No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 2, }, @@ -320,66 +161,6 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { } } -func TestTCF1FetcherDynamicLoadListDoesntExist(t *testing.T) { - // Loads the first vendor list during initialization by setting the latest vendor list version to 1. - // All other vendor list load attempts will be done dynamically. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 1, - vendorLists: map[int]string{ - 1: tcf1VendorList1, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fetch - No Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "Fetch - Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fetch - Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: false, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fetch - No Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: false, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - } - - for _, test := range testCases { - runTest(t, test, 1, server) - } -} - func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor list load attempts will be done dynamically. @@ -395,31 +176,8 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { testCases := []test{ { - description: "Fetch - No Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "Fetch - Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fetch: true, - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "No Fetch - Fallback - Vendor Doesn't Exist", + description: "Fallback - Vendor Doesn't Exist", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: true, vendorListVersion: 2, }, @@ -428,9 +186,8 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { }, }, { - description: "No Fetch - No Fallback - Vendor Doesn't Exist", + description: "No Fallback - Vendor Doesn't Exist", setup: testSetup{ - enableTCF1Fetch: false, enableTCF1Fallback: false, vendorListVersion: 2, }, @@ -445,38 +202,6 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { } } -func TestTCF1FetcherThrottling(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 1, - vendorLists: map[int]string{ - 1: tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1}}}, - }), - 2: tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 2, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1, 2}}}, - }), - 3: tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 3, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{1, 2, 3}}}, - }), - }, - }))) - defer server.Close() - - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) - - // Dynamically Load List 2 Successfully - _, errList1 := fetcher(context.Background(), 2) - assert.NoError(t, errList1) - - // Fail To Load List 3 Due To Rate Limiting - // - The request is rate limited after dynamically list 2. - _, errList2 := fetcher(context.Background(), 3) - assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") -} - func TestTCF2FetcherThrottling(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, @@ -509,22 +234,6 @@ func TestTCF2FetcherThrottling(t *testing.T) { assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF1MalformedVendorlist(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 1, - vendorLists: map[int]string{ - 1: "malformed", - }, - }))) - defer server.Close() - - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) - _, err := fetcher(context.Background(), 1) - - // Fetching should fail since vendor list could not be unmarshalled. - assert.Error(t, err) -} - func TestTCF2MalformedVendorlist(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, @@ -541,18 +250,6 @@ func TestTCF2MalformedVendorlist(t *testing.T) { assert.Error(t, err) } -func TestTCF1ServerUrlInvalid(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - server.Close() - - invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" } - - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf1SpecVersion) - _, err := fetcher(context.Background(), 1) - - assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") -} - func TestTCF2ServerUrlInvalid(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() @@ -565,16 +262,6 @@ func TestTCF2ServerUrlInvalid(t *testing.T) { assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF1ServerUnavailable(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - server.Close() - - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion) - _, err := fetcher(context.Background(), 1) - - assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") -} - func TestTCF2ServerUnavailable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() @@ -740,7 +427,6 @@ type test struct { } type testSetup struct { - enableTCF1Fetch bool enableTCF1Fallback bool vendorListVersion uint16 } @@ -754,7 +440,6 @@ type testExpected struct { func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Server) { config := testConfig() - config.TCF1.FetchGVL = test.setup.enableTCF1Fetch if test.setup.enableTCF1Fallback { config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" } From 27d1b8f4e918317f470b96b65e591bd3cbaa0d48 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Wed, 3 Feb 2021 20:10:45 +0200 Subject: [PATCH 313/603] Adform adapter: digitrust cleanup (#1690) * adform secure endpoint as default setting * digitrust cleanup --- adapters/adform/adform.go | 56 +------------------ adapters/adform/adform_test.go | 17 ++---- .../exemplary/multiformat-impression.json | 2 +- .../exemplary/single-banner-impression.json | 2 +- .../exemplary/single-video-impression.json | 2 +- .../adformtest/supplemental/user-nil.json | 2 +- config/config.go | 2 +- 7 files changed, 11 insertions(+), 72 deletions(-) diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 5eeeb132786..e84df5b8cc7 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -44,23 +44,11 @@ type adformRequest struct { adUnits []*adformAdUnit gdprApplies string consent string - digitrust *adformDigitrust currency string eids string url string } -type adformDigitrust struct { - Id string `json:"id"` - Version int `json:"version"` - Keyv int `json:"keyv"` - Privacy adformDigitrustPrivacy `json:"privacy"` -} - -type adformDigitrustPrivacy struct { - Optout bool `json:"optout"` -} - type adformAdUnit struct { MasterTagId json.Number `json:"mid"` PriceType string `json:"priceType,omitempty"` @@ -216,24 +204,6 @@ func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder gdprApplies = "" } consent := request.ParseConsent() - var digitrustData *openrtb_ext.ExtUserDigiTrust - if request.User != nil { - var extUser *openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { - digitrustData = extUser.DigiTrust - } - } - - var digitrust *adformDigitrust = nil - if digitrustData != nil { - digitrust = new(adformDigitrust) - digitrust.Id = digitrustData.ID - digitrust.Keyv = digitrustData.KeyV - digitrust.Version = 1 - digitrust.Privacy = adformDigitrustPrivacy{ - Optout: digitrustData.Pref != 0, - } - } return &adformRequest{ adUnits: adUnits, @@ -247,7 +217,6 @@ func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder tid: request.Tid, gdprApplies: gdprApplies, consent: consent, - digitrust: digitrust, currency: defaultCurrency, }, nil } @@ -371,18 +340,9 @@ func (r *adformRequest) buildAdformHeaders(a *AdformAdapter) http.Header { header.Set("Referer", r.referer) } - cookie := make([]string, 0, 2) if r.userId != "" { - cookie = append(cookie, fmt.Sprintf("uid=%s", r.userId)) - } - if r.digitrust != nil { - if digitrustBytes, err := json.Marshal(r.digitrust); err == nil { - digitrust := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(digitrustBytes) - // Cookie name and structure are described here: https://github.com/digi-trust/dt-cdn/wiki/Cookies-for-Platforms - cookie = append(cookie, fmt.Sprintf("DigiTrust.v1.identity=%s", digitrust)) - } + header.Set("Cookie", fmt.Sprintf("uid=%s;", r.userId)) } - header.Set("Cookie", strings.Join(cookie, ";")) return header } @@ -506,27 +466,14 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro eids := "" consent := "" - var digitrustData *openrtb_ext.ExtUserDigiTrust if request.User != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { consent = extUser.Consent - digitrustData = extUser.DigiTrust eids = encodeEids(extUser.Eids) } } - var digitrust *adformDigitrust = nil - if digitrustData != nil { - digitrust = new(adformDigitrust) - digitrust.Id = digitrustData.ID - digitrust.Keyv = digitrustData.KeyV - digitrust.Version = 1 - digitrust.Privacy = adformDigitrustPrivacy{ - Optout: digitrustData.Pref != 0, - } - } - requestCurrency := defaultCurrency if len(request.Cur) != 0 { hasDefaultCurrency := false @@ -552,7 +499,6 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro tid: tid, gdprApplies: gdprApplies, consent: consent, - digitrust: digitrust, currency: requestCurrency, eids: eids, url: url, diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 8ce55a1231c..558453e4030 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -27,7 +27,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "http://adx.adform.net/adx"}) + Endpoint: "https://adx.adform.net/adx"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -300,7 +300,7 @@ func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer func TestOpenRTBRequest(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "http://adx.adform.net"}) + Endpoint: "https://adx.adform.net"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -525,12 +525,6 @@ func getRegs() openrtb.Regs { } func getUserExt() []byte { - digitrust := openrtb_ext.ExtUserDigiTrust{ - ID: "digitrustId", - KeyV: 1, - Pref: 0, - } - eids := []openrtb_ext.ExtUserEid{ { Source: "test.com", @@ -556,9 +550,8 @@ func getUserExt() []byte { } userExt := openrtb_ext.ExtUser{ - Eids: eids, - Consent: "abc", - DigiTrust: &digitrust, + Eids: eids, + Consent: "abc", } userExtData, err := json.Marshal(userExt) if err == nil { @@ -630,7 +623,7 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo if ok, err := equal(testData.referrer, r.Header.Get("Referer"), "Referer"); !ok { return err } - if ok, err := equal(fmt.Sprintf("uid=%s;DigiTrust.v1.identity=eyJpZCI6ImRpZ2l0cnVzdElkIiwidmVyc2lvbiI6MSwia2V5diI6MSwicHJpdmFjeSI6eyJvcHRvdXQiOmZhbHNlfX0", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok { + if ok, err := equal(fmt.Sprintf("uid=%s;", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok { return err } return nil diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json index efd4aca63e2..5b3067ab927 100644 --- a/adapters/adform/adformtest/exemplary/multiformat-impression.json +++ b/adapters/adform/adformtest/exemplary/multiformat-impression.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE" + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE" }, "mockResponse": { "status": 200, diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json index fd7f3cde526..8a5f81c8edb 100644 --- a/adapters/adform/adformtest/exemplary/single-banner-impression.json +++ b/adapters/adform/adformtest/exemplary/single-banner-impression.json @@ -23,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" }, "mockResponse": { "status": 200, diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json index e22977e6523..383e091b3f7 100644 --- a/adapters/adform/adformtest/exemplary/single-video-impression.json +++ b/adapters/adform/adformtest/exemplary/single-video-impression.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE" + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE" }, "mockResponse": { "status": 200, diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json index 34b857d556a..5f02fe85971 100644 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ b/adapters/adform/adformtest/supplemental/user-nil.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q" + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q" }, "mockResponse": { "status": 204 diff --git a/config/config.go b/config/config.go index f040040bf64..67964f0dde3 100755 --- a/config/config.go +++ b/config/config.go @@ -794,7 +794,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") - v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx") + v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") From 3b93511eb8076a6e47a65f83930c09a425b9b759 Mon Sep 17 00:00:00 2001 From: Vladyslav Laktionov Date: Thu, 4 Feb 2021 08:12:55 +0200 Subject: [PATCH 314/603] New Adapter: DecenterAds (#1669) Co-authored-by: vlad --- adapters/decenterads/decenterads.go | 124 +++++++++++++++++ adapters/decenterads/decenterads_test.go | 18 +++ .../exemplary/simple-banner.json | 128 ++++++++++++++++++ .../exemplary/simple-video.json | 116 ++++++++++++++++ .../exemplary/simple-web-banner.json | 126 +++++++++++++++++ .../decenteradstest/params/race/banner.json | 3 + .../decenteradstest/params/race/video.json | 3 + .../supplemental/bad-imp-ext.json | 41 ++++++ .../supplemental/bad_response.json | 83 ++++++++++++ .../supplemental/bad_status_code.json | 77 +++++++++++ .../supplemental/imp_ext_empty_object.json | 37 +++++ .../supplemental/imp_ext_string.json | 37 +++++ .../supplemental/status-204.json | 78 +++++++++++ .../supplemental/status-404.json | 83 ++++++++++++ adapters/decenterads/params_test.go | 48 +++++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_decenterads.go | 5 + static/bidder-info/decenterads.yaml | 14 ++ static/bidder-params/decenterads.json | 18 +++ usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1046 insertions(+) create mode 100644 adapters/decenterads/decenterads.go create mode 100644 adapters/decenterads/decenterads_test.go create mode 100644 adapters/decenterads/decenteradstest/exemplary/simple-banner.json create mode 100644 adapters/decenterads/decenteradstest/exemplary/simple-video.json create mode 100644 adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json create mode 100644 adapters/decenterads/decenteradstest/params/race/banner.json create mode 100644 adapters/decenterads/decenteradstest/params/race/video.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/bad_response.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/bad_status_code.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/status-204.json create mode 100644 adapters/decenterads/decenteradstest/supplemental/status-404.json create mode 100644 adapters/decenterads/params_test.go create mode 100644 openrtb_ext/imp_decenterads.go create mode 100644 static/bidder-info/decenterads.yaml create mode 100644 static/bidder-params/decenterads.json diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go new file mode 100644 index 00000000000..5719bf1e4b3 --- /dev/null +++ b/adapters/decenterads/decenterads.go @@ -0,0 +1,124 @@ +package decenterads + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + impressions := request.Imp + result := make([]*adapters.RequestData, 0, len(impressions)) + errs := make([]error, 0, len(impressions)) + + for _, impression := range impressions { + var impExt map[string]json.RawMessage + if err := json.Unmarshal(impression.Ext, &impExt); err != nil { + errs = append(errs, fmt.Errorf("unable to parse bidder parameers: %s", err)) + continue + } + + bidderExt, bidderExtExists := impExt["bidder"] + if !bidderExtExists || len(bidderExt) == 0 { + errs = append(errs, errors.New("bidder parameters required")) + continue + } + + impression.Ext = bidderExt + request.Imp = []openrtb.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + result = append(result, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: body, + Headers: headers, + }) + } + + request.Imp = impressions + return result, errs +} + +func (a *adapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + switch responseData.StatusCode { + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + return nil, []error{&errortypes.BadInput{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode), + }} + case http.StatusOK: + break + default: + return nil, []error{&errortypes.BadServerResponse{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode), + }} + } + + var bidResponse openrtb.BidResponse + err := json.Unmarshal(responseData.Body, &bidResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + for _, seatBid := range bidResponse.SeatBid { + for i := range seatBid.Bid { + response.Bids = append(response.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: getMediaTypeForImp(seatBid.Bid[i].ImpID, request.Imp), + }) + } + } + + return response, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio + } + } + } + return openrtb_ext.BidTypeBanner +} + +// Builder builds a new instance of the DecenterAds adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/decenterads/decenterads_test.go b/adapters/decenterads/decenterads_test.go new file mode 100644 index 00000000000..ca86e89187c --- /dev/null +++ b/adapters/decenterads/decenterads_test.go @@ -0,0 +1,18 @@ +package decenterads + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderDecenterAds, config.Adapter{ + Endpoint: "http://example.com/?c=o&m=ortb"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "decenteradstest", bidder) +} diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-banner.json b/adapters/decenterads/decenteradstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..fd2c09d01f7 --- /dev/null +++ b/adapters/decenterads/decenteradstest/exemplary/simple-banner.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "3" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "3" + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-video.json b/adapters/decenterads/decenteradstest/exemplary/simple-video.json new file mode 100644 index 00000000000..b85d302b662 --- /dev/null +++ b/adapters/decenterads/decenteradstest/exemplary/simple-video.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "3" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "placementId": "3" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "decenterads" + } + ], + "cur": "USD" + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json b/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..8f8aa0e94b1 --- /dev/null +++ b/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "3" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "3" + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "decenterads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/decenterads/decenteradstest/params/race/banner.json b/adapters/decenterads/decenteradstest/params/race/banner.json new file mode 100644 index 00000000000..dbdac1ad995 --- /dev/null +++ b/adapters/decenterads/decenteradstest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "6" +} \ No newline at end of file diff --git a/adapters/decenterads/decenteradstest/params/race/video.json b/adapters/decenterads/decenteradstest/params/race/video.json new file mode 100644 index 00000000000..6e2e0b3803b --- /dev/null +++ b/adapters/decenterads/decenteradstest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "7" +} \ No newline at end of file diff --git a/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json b/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..83455ca70b6 --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json @@ -0,0 +1,41 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "decenterads": { + "placementId": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "bidder parameters required", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/bad_response.json b/adapters/decenterads/decenteradstest/supplemental/bad_response.json new file mode 100644 index 00000000000..9d9d977b14a --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/bad_response.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "1" + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json b/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json new file mode 100644 index 00000000000..4a81da360e4 --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "1" + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 400", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json b/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json new file mode 100644 index 00000000000..db4053f2e1f --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "bidder parameters required", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json b/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json new file mode 100644 index 00000000000..db938d6a652 --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "unable to parse bidder parameers: json: cannot unmarshal string into Go value of type map[string]json.RawMessage", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/status-204.json b/adapters/decenterads/decenteradstest/supplemental/status-204.json new file mode 100644 index 00000000000..3eff0fdcd8d --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/status-204.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "1" + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "expectedBidResponses": [], + "mockResponse": { + "status": 204, + "body": {} + } + } + ] +} diff --git a/adapters/decenterads/decenteradstest/supplemental/status-404.json b/adapters/decenterads/decenteradstest/supplemental/status-404.json new file mode 100644 index 00000000000..a162dd897ea --- /dev/null +++ b/adapters/decenterads/decenteradstest/supplemental/status-404.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "1" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "placementId": "1" + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 404", + "comparison": "literal" + } + ] +} diff --git a/adapters/decenterads/params_test.go b/adapters/decenterads/params_test.go new file mode 100644 index 00000000000..3d3708be789 --- /dev/null +++ b/adapters/decenterads/params_test.go @@ -0,0 +1,48 @@ +package decenterads + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// TestValidParams makes sure that the decenterads schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderDecenterAds, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected decenterads params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the decenterads schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderDecenterAds, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": "11"}`, +} + +var invalidParams = []string{ + `{"id": "456"}`, + `{"placementid": "3456"}`, + `{"placement_id": 346}`, + `{"placementID": ""}`, + `{"placementId": 234}`, +} diff --git a/config/config.go b/config/config.go index 67964f0dde3..ae1f62e90b4 100755 --- a/config/config.go +++ b/config/config.go @@ -590,6 +590,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") + // openrtb_ext.BidderDecenterAds doesn't have a good default. // openrtb_ext.BidderDMX doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") @@ -828,6 +829,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") + v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb") v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid") v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 8da57d6ec59..f05a4d817fe 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -35,6 +35,7 @@ import ( "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" + "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" "github.com/prebid/prebid-server/adapters/emx_digital" @@ -136,6 +137,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderConversant: conversant.Builder, openrtb_ext.BidderCpmstar: cpmstar.Builder, openrtb_ext.BidderDatablocks: datablocks.Builder, + openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderEmxDigital: emx_digital.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c0839632c7e..576b1d1ac6f 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -74,6 +74,7 @@ const ( BidderCpmstar BidderName = "cpmstar" BidderDatablocks BidderName = "datablocks" BidderDmx BidderName = "dmx" + BidderDecenterAds BidderName = "decenterads" BidderDeepintent BidderName = "deepintent" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" @@ -173,6 +174,7 @@ func CoreBidderNames() []BidderName { BidderConversant, BidderCpmstar, BidderDatablocks, + BidderDecenterAds, BidderDeepintent, BidderDmx, BidderEmxDigital, diff --git a/openrtb_ext/imp_decenterads.go b/openrtb_ext/imp_decenterads.go new file mode 100644 index 00000000000..101b85c13ad --- /dev/null +++ b/openrtb_ext/imp_decenterads.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpDecenterAds struct { + PlacementID string `json:"placementId"` +} diff --git a/static/bidder-info/decenterads.yaml b/static/bidder-info/decenterads.yaml new file mode 100644 index 00000000000..c9b349cd78a --- /dev/null +++ b/static/bidder-info/decenterads.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@decenterads.com" +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/decenterads.json b/static/bidder-params/decenterads.json new file mode 100644 index 00000000000..970e99038a0 --- /dev/null +++ b/static/bidder-params/decenterads.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DecenterAds Adapter Params", + "description": "A schema which validates params accepted by the DecenterAds adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the DecenterAds placement" + }, + "customParams": { + "type": "object", + "description": "User-defined targeting key-value pairs." + } + }, + "required" : [ "placementId" ] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index bca8623d98b..73f0e8861c5 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -101,6 +101,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdot: true, openrtb_ext.BidderAdprime: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderDecenterAds: true, openrtb_ext.BidderInMobi: true, openrtb_ext.BidderKidoz: true, openrtb_ext.BidderKubient: true, From 3b9f61aec86aed9a69316c45310664cf17ef0290 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 4 Feb 2021 08:39:06 -0500 Subject: [PATCH 315/603] Handle empty consent string during cookie sync and setuid (#1671) * Handle empty consent string during cookie sync and setuid * Remove todo comment * Make auction test table driven and convert GDPR impl normalize method to pass by value * Moved legacy auction endpoint signal parsing into its own method and removed unnecessary test cases * Fix SignalParse method to return nil for error when raw signal is empty and other PR feedback --- endpoints/auction.go | 23 +++---- endpoints/auction_test.go | 74 ++++++++++++++------ endpoints/cookie_sync.go | 5 +- endpoints/cookie_sync_test.go | 4 +- endpoints/setuid.go | 58 +++++++++------- endpoints/setuid_test.go | 17 ++--- exchange/utils_test.go | 4 +- gdpr/gdpr.go | 24 ++++++- gdpr/gdpr_test.go | 52 ++++++++++++++ gdpr/impl.go | 54 +++++++++------ gdpr/impl_test.go | 123 ++++++++++++++++++++++++---------- 11 files changed, 306 insertions(+), 132 deletions(-) diff --git a/endpoints/auction.go b/endpoints/auction.go index 0604c224458..92e9769d59e 100644 --- a/endpoints/auction.go +++ b/endpoints/auction.go @@ -189,21 +189,16 @@ func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels } func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { - switch gdprPrivacyPolicy.Signal { - case "0": - return true - case "1": - if gdprPrivacyPolicy.Consent == "" { - return false - } - fallthrough - default: - if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprPrivacyPolicy.Consent); !canSync || err != nil { - return false - } - canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprPrivacyPolicy.Consent) - return canSync && err == nil + gdprSignal := gdpr.SignalAmbiguous + if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil { + gdprSignal = signal + } + + if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync { + return false } + canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent) + return canSync && err == nil } // cache video bids only for Web diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index db26a84b949..ed9a526d760 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -22,6 +22,8 @@ import ( gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/usersync/usersyncers" "github.com/spf13/viper" + + "github.com/stretchr/testify/assert" ) func TestSortBidsAndAddKeywordsForMobile(t *testing.T) { @@ -376,32 +378,64 @@ func TestCacheVideoOnly(t *testing.T) { } func TestShouldUsersync(t *testing.T) { - doTest := func(gdprApplies string, consent string, allowBidderSync bool, allowHostCookies bool, expectAllow bool) { - t.Helper() + tests := []struct { + description string + signal string + allowHostCookies bool + allowBidderSync bool + wantAllow bool + }{ + { + description: "Don't sync - GDPR on, host cookies disallows and bidder sync disallows", + signal: "1", + allowHostCookies: false, + allowBidderSync: false, + wantAllow: false, + }, + { + description: "Don't sync - GDPR on, host cookies disallows and bidder sync allows", + signal: "1", + allowHostCookies: false, + allowBidderSync: true, + wantAllow: false, + }, + { + description: "Don't sync - GDPR on, host cookies allows and bidder sync disallows", + signal: "1", + allowHostCookies: true, + allowBidderSync: false, + wantAllow: false, + }, + { + description: "Sync - GDPR on, host cookies allows and bidder sync allows", + signal: "1", + allowHostCookies: true, + allowBidderSync: true, + wantAllow: true, + }, + { + description: "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows", + signal: "2", + allowHostCookies: false, + allowBidderSync: false, + wantAllow: false, + }, + } + + for _, tt := range tests { deps := auction{ - cfg: nil, - syncers: nil, gdprPerms: &auctionMockPermissions{ - allowBidderSync: allowBidderSync, - allowHostCookies: allowHostCookies, + allowBidderSync: tt.allowBidderSync, + allowHostCookies: tt.allowHostCookies, }, - metricsEngine: nil, } gdprPrivacyPolicy := gdprPolicy.Policy{ - Signal: gdprApplies, - Consent: consent, + Signal: tt.signal, } - allowSyncs := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) - if allowSyncs != expectAllow { - t.Errorf("Expected syncs: %t, allowed syncs: %t", expectAllow, allowSyncs) - } + allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) + assert.Equal(t, tt.wantAllow, allow, tt.description) } - doTest("0", "", false, false, true) - doTest("1", "", true, true, false) - doTest("1", "a", true, false, false) - doTest("1", "a", false, true, false) - doTest("1", "a", true, true, true) } type auctionMockPermissions struct { @@ -412,11 +446,11 @@ type auctionMockPermissions struct { allowID bool } -func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { return m.allowHostCookies, nil } -func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { return m.allowBidderSync, nil } diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 21876af9efd..bf3935f0535 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -248,13 +248,14 @@ func (req *cookieSyncRequest) filterForGDPR(permissions gdpr.Permissions) { return } - if allowSync, err := permissions.HostCookiesAllowed(context.Background(), req.Consent); err != nil || !allowSync { + // At this point we know the gdpr signal is Yes because the upstream call to parseRequest already denormalized the signal if it was ambiguous + if allowSync, err := permissions.HostCookiesAllowed(context.Background(), gdpr.SignalYes, req.Consent); err != nil || !allowSync { req.Bidders = nil return } for i := 0; i < len(req.Bidders); i++ { - if allowSync, err := permissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(req.Bidders[i]), req.Consent); err != nil || !allowSync { + if allowSync, err := permissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(req.Bidders[i]), gdpr.SignalYes, req.Consent); err != nil || !allowSync { req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...) i-- } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index c790fcd9d74..a6352387786 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -245,11 +245,11 @@ type gdprPerms struct { allowedBidders map[openrtb_ext.BidderName]usersync.Usersyncer } -func (g *gdprPerms) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (g *gdprPerms) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { return g.allowHost, nil } -func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { _, ok := g.allowedBidders[bidder] return ok, nil } diff --git a/endpoints/setuid.go b/endpoints/setuid.go index caa3ae1766d..4bff02acf37 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -143,32 +143,38 @@ func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool { return result } -func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (bool, int, string) { - switch gdprEnabled { - case "0": - return false, 0, "" - case "1": - if gdprConsent == "" { - return true, http.StatusBadRequest, "gdpr_consent is required when gdpr=1" - } - fallthrough - case "": - if allowed, err := perms.HostCookiesAllowed(context.Background(), gdprConsent); err != nil { - if _, ok := err.(*gdpr.ErrorMalformedConsent); ok { - return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error() - } else { - // We can't really distinguish between requests that are for a new version of the global vendor list, and - // ones which are simply malformed (version number is much too large). - // Since we try to fetch new versions as requests come in for them, PBS *should* self-correct - // rather quickly, meaning that most of these will be malformed strings. - return true, http.StatusBadRequest, "No global vendor list was available to interpret this consent string. If this is a new, valid version, it should become available soon." - } - } else if !allowed { - return true, http.StatusOK, "The gdpr_consent string prevents cookies from being saved" - } else { - return false, 0, "" - } - default: +func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (shouldReturn bool, status int, body string) { + + if gdprEnabled != "" && gdprEnabled != "0" && gdprEnabled != "1" { return true, http.StatusBadRequest, "the gdpr query param must be either 0 or 1. You gave " + gdprEnabled } + + if gdprEnabled == "1" && gdprConsent == "" { + return true, http.StatusBadRequest, "gdpr_consent is required when gdpr=1" + } + + gdprSignal := gdpr.SignalAmbiguous + + if i, err := strconv.Atoi(gdprEnabled); err == nil { + gdprSignal = gdpr.Signal(i) + } + + allowed, err := perms.HostCookiesAllowed(context.Background(), gdprSignal, gdprConsent) + if err != nil { + if _, ok := err.(*gdpr.ErrorMalformedConsent); ok { + return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error() + } + + // We can't really distinguish between requests that are for a new version of the global vendor list, and + // ones which are simply malformed (version number is much too large). + // Since we try to fetch new versions as requests come in for them, PBS *should* self-correct + // rather quickly, meaning that most of these will be malformed strings. + return true, http.StatusBadRequest, "No global vendor list was available to interpret this consent string. If this is a new, valid version, it should become available soon." + } + + if allowed { + return false, 0, "" + } + + return true, http.StatusOK, "The gdpr_consent string prevents cookies from being saved" } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 602859582c0..fc98608ef9f 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -101,12 +101,13 @@ func TestSetUIDEndpoint(t *testing.T) { description: "Add the uid for the requested bidder to the list of existing syncs", }, { - uri: "/setuid?bidder=pubmatic&uid=123&gdpr=0", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - expectedSyncs: map[string]string{"pubmatic": "123"}, - expectedResponseCode: http.StatusOK, - description: "Don't care about GDPR consent if GDPR is set to 0", + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=0", + validFamilyNames: []string{"pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedResponseCode: http.StatusOK, + description: "Don't care about GDPR consent if GDPR is set to 0", }, { uri: "/setuid?bidder=pubmatic&uid=123", @@ -426,7 +427,7 @@ type mockPermsSetUID struct { allowPI bool } -func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { var err error if g.errorHost { err = errors.New("something went wrong") @@ -434,7 +435,7 @@ func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, consent string return g.allowHost, err } -func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { return false, nil } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index e679397b1dd..0407b3c5e0e 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -24,11 +24,11 @@ type permissionsMock struct { personalInfoAllowedError error } -func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) { return true, nil } -func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdpr gdpr.Signal, consent string) (bool, error) { return true, nil } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index bfc238e12a3..f24fd6c56f5 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -3,9 +3,11 @@ package gdpr import ( "context" "net/http" + "strconv" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -13,12 +15,12 @@ type Permissions interface { // Determines whether or not the host company is allowed to read/write cookies. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - HostCookiesAllowed(ctx context.Context, consent string) (bool, error) + HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) // Determines whether or not the given bidder is allowed to user personal info for ad targeting. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) + BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) // Determines whether or not to send PI information to a bidder, or mask it out. // @@ -65,3 +67,21 @@ type ErrorMalformedConsent struct { func (e *ErrorMalformedConsent) Error() string { return "malformed consent string " + e.consent + ": " + e.cause.Error() } + +// SignalParse parses a raw signal and returns +func SignalParse(rawSignal string) (Signal, error) { + if rawSignal == "" { + return SignalAmbiguous, nil + } + + i, err := strconv.Atoi(rawSignal) + + if err != nil { + return SignalAmbiguous, err + } + if i != 0 && i != 1 { + return SignalAmbiguous, &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"} + } + + return Signal(i), nil +} diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 902bf14e662..5048cf118f5 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -48,3 +48,55 @@ func TestNewPermissions(t *testing.T) { assert.IsType(t, tt.wantType, perms, tt.description) } } + +func TestSignalParse(t *testing.T) { + tests := []struct { + description string + rawSignal string + wantSignal Signal + wantError bool + }{ + { + description: "valid raw signal is 0", + rawSignal: "0", + wantSignal: SignalNo, + wantError: false, + }, + { + description: "Valid signal - raw signal is 1", + rawSignal: "1", + wantSignal: SignalYes, + wantError: false, + }, + { + description: "Valid signal - raw signal is empty", + rawSignal: "", + wantSignal: SignalAmbiguous, + wantError: false, + }, + { + description: "Invalid signal - raw signal is -1", + rawSignal: "-1", + wantSignal: SignalAmbiguous, + wantError: true, + }, + { + description: "Invalid signal - raw signal is abc", + rawSignal: "abc", + wantSignal: SignalAmbiguous, + wantError: true, + }, + } + + for _, tt := range tests { + signal, err := SignalParse(tt.rawSignal) + + assert.Equal(t, tt.wantSignal, signal, tt.description) + + if tt.wantError { + assert.NotNil(t, err, tt.description) + } else { + assert.Nil(t, err, tt.description) + } + } +} diff --git a/gdpr/impl.go b/gdpr/impl.go index c7998251783..06b625da95c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -33,20 +33,28 @@ type permissionsImpl struct { fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } -func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { + gdprSignal = p.normalizeGDPR(gdprSignal) + + if gdprSignal == SignalNo { + return true, nil + } + return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent) } -func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { + gdprSignal = p.normalizeGDPR(gdprSignal) + + if gdprSignal == SignalNo { + return true, nil + } + id, ok := p.vendorIDs[bidder] if ok { return p.allowSync(ctx, id, consent) } - if consent == "" { - return p.cfg.UsersyncIfAmbiguous, nil - } - return false, nil } @@ -55,13 +63,7 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt return true, true, true, nil } - if gdprSignal == SignalAmbiguous { - if p.cfg.UsersyncIfAmbiguous { - gdprSignal = SignalNo - } else { - gdprSignal = SignalYes - } - } + gdprSignal = p.normalizeGDPR(gdprSignal) if gdprSignal == SignalNo { return true, true, true, nil @@ -82,10 +84,22 @@ func (p *permissionsImpl) defaultVendorPermissions() (allowPI bool, allowGeo boo return false, false, false, nil } +func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { + if gdprSignal != SignalAmbiguous { + return gdprSignal + } + + if p.cfg.UsersyncIfAmbiguous { + return SignalNo + } + + return SignalYes +} + func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) { - // If we're not given a consent string, respect the preferences in the app config. + if consent == "" { - return p.cfg.UsersyncIfAmbiguous, nil + return false, nil } parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) @@ -229,18 +243,18 @@ type AllowHostCookies struct { } // HostCookiesAllowed always returns true -func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { return true, nil } // Exporting to allow for easy test setups type AlwaysAllow struct{} -func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { return true, nil } -func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { return true, nil } @@ -251,11 +265,11 @@ func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext // Exporting to allow for easy test setups type AlwaysFail struct{} -func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { +func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { return false, nil } -func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { return false, nil } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index a7d4b26af67..45d2ba43ce3 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNoConsentButAllowByDefault(t *testing.T) { +func TestDisallowOnEmptyConsent(t *testing.T) { perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 3, @@ -27,34 +27,27 @@ func TestNoConsentButAllowByDefault(t *testing.T) { tcf2SpecVersion: failedListFetcher, }, } - allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") - assertBoolsEqual(t, true, allowSync) - assertNilErr(t, err) - allowSync, err = perms.HostCookiesAllowed(context.Background(), "") - assertBoolsEqual(t, true, allowSync) - assertNilErr(t, err) -} - -func TestNoConsentAndRejectByDefault(t *testing.T) { - perms := permissionsImpl{ - cfg: config.GDPR{ - HostVendorID: 3, - UsersyncIfAmbiguous: false, - }, - vendorIDs: nil, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: failedListFetcher, - tcf2SpecVersion: failedListFetcher, - }, - } - allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "") + allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, "") assertBoolsEqual(t, false, allowSync) assertNilErr(t, err) - allowSync, err = perms.HostCookiesAllowed(context.Background(), "") + allowSync, err = perms.HostCookiesAllowed(context.Background(), SignalYes, "") assertBoolsEqual(t, false, allowSync) assertNilErr(t, err) } +func TestAllowOnSignalNo(t *testing.T) { + perms := permissionsImpl{} + emptyConsent := "" + + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalNo, emptyConsent) + assert.Equal(t, true, allowSync) + assert.Nil(t, err) + + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalNo, emptyConsent) + assert.Equal(t, true, allowSync) + assert.Nil(t, err) +} + func TestAllowedSyncs(t *testing.T) { vendorListData := tcf1MarshalVendorList(tcf1VendorList{ VendorListVersion: 1, @@ -81,11 +74,11 @@ func TestAllowedSyncs(t *testing.T) { }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) } @@ -116,11 +109,11 @@ func TestProhibitedPurposes(t *testing.T) { }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -151,11 +144,11 @@ func TestProhibitedVendors(t *testing.T) { }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -171,7 +164,7 @@ func TestMalformedConsent(t *testing.T) { }, } - sync, err := perms.HostCookiesAllowed(context.Background(), "BON") + sync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON") assertErr(t, err, true) assertBoolsEqual(t, false, sync) } @@ -618,11 +611,11 @@ func TestAllowSyncTCF2(t *testing.T) { } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") } @@ -648,11 +641,11 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { perms.cfg.HostVendorID = 8 // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } @@ -677,12 +670,12 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { perms.cfg.HostVendorID = 10 // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 - allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") // Permission disallowed due to consent string not including vendor 10. - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing BidderSyncAllowed") assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } @@ -750,3 +743,61 @@ func assertStringsEqual(t *testing.T, expected string, actual string) { t.Errorf("Expected %s, got %s", expected, actual) } } + +func TestNormalizeGDPR(t *testing.T) { + tests := []struct { + description string + userSyncIfAmbiguous bool + giveSignal Signal + wantSignal Signal + }{ + { + description: "Don't normalize - Signal No and userSyncIfAmbiguous false", + userSyncIfAmbiguous: false, + giveSignal: SignalNo, + wantSignal: SignalNo, + }, + { + description: "Don't normalize - Signal No and userSyncIfAmbiguous true", + userSyncIfAmbiguous: true, + giveSignal: SignalNo, + wantSignal: SignalNo, + }, + { + description: "Don't normalize - Signal Yes and userSyncIfAmbiguous false", + userSyncIfAmbiguous: false, + giveSignal: SignalYes, + wantSignal: SignalYes, + }, + { + description: "Don't normalize - Signal Yes and userSyncIfAmbiguous true", + userSyncIfAmbiguous: true, + giveSignal: SignalYes, + wantSignal: SignalYes, + }, + { + description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous false", + userSyncIfAmbiguous: false, + giveSignal: SignalAmbiguous, + wantSignal: SignalYes, + }, + { + description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous true", + userSyncIfAmbiguous: true, + giveSignal: SignalAmbiguous, + wantSignal: SignalNo, + }, + } + + for _, tt := range tests { + perms := permissionsImpl{ + cfg: config.GDPR{ + UsersyncIfAmbiguous: tt.userSyncIfAmbiguous, + }, + } + + normalizedSignal := perms.normalizeGDPR(tt.giveSignal) + + assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) + } +} From 67b9c2500684eae07d411ebccc88ad3df3b9141d Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Thu, 11 Feb 2021 16:51:59 +0100 Subject: [PATCH 316/603] Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes --- adapters/tappx/tappx_test.go | 6 +- .../exemplary/single-banner-impression.json | 10 +- .../exemplary/single-banner-site.json | 124 ++++++++++++++++ .../exemplary/single-video-impression.json | 10 +- .../exemplary/single-video-site.json | 136 ++++++++++++++++++ .../tappx/tappxtest/params/race/banner.json | 4 +- .../tappx/tappxtest/params/race/video.json | 4 +- .../tappxtest/supplemental/204status.json | 10 +- .../banner-impression-badhost.json | 4 +- .../banner-impression-noendpoint.json | 2 +- .../banner-impression-nohost.json | 2 +- .../supplemental/banner-impression-nokey.json | 4 +- .../tappxtest/supplemental/bidfloor.json | 10 +- .../supplemental/http-err-status.json | 10 +- .../supplemental/http-err-status2.json | 10 +- .../supplemental/wrong-imp-ext-1.json | 4 +- adapters/tappx/usersync.go | 12 ++ adapters/tappx/usersync_test.go | 35 +++++ config/config.go | 4 +- static/bidder-info/tappx.yaml | 4 + usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 2 +- 22 files changed, 361 insertions(+), 48 deletions(-) create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-site.json create mode 100644 adapters/tappx/tappxtest/exemplary/single-video-site.json create mode 100644 adapters/tappx/usersync.go create mode 100644 adapters/tappx/usersync_test.go mode change 100755 => 100644 config/config.go mode change 100755 => 100644 usersync/usersyncers/syncer.go diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 5346b82b694..187190862ea 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -12,7 +12,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "https://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -30,7 +30,7 @@ func TestEndpointTemplateMalformed(t *testing.T) { func TestTsValue(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "https://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`https://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url) + match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url) if err != nil { t.Errorf("Error while running regex validation: %s", err.Error()) return diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index 3a365db645e..4f5b792fe4f 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -47,8 +47,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json new file mode 100644 index 00000000000..ef61b0e2567 --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index 49cb3c7e568..7a469b07fb6 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -15,8 +15,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -51,8 +51,8 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "tappxkey": "pub-12345-android-9876" } } diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json new file mode 100644 index 00000000000..9ee2f8f4187 --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "tappxkey": "pub-12345-site-9876" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [ + { + "bid": [ + { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "cid": "1001", + "crid": "2002", + "adid": "2002", + "adm": "", + "cat": [ + "IAB2" + ], + "adomain": [ + "video-example.com" + ] + } + ] + } + ], + "bidid": "wehM-93KGr0" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "adm": "", + "adomain": [ + "video-example.com" + ], + "cid": "1001", + "adid": "2002", + "crid": "2002", + "cat": [ + "IAB2" + ] + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/params/race/banner.json b/adapters/tappx/tappxtest/params/race/banner.json index 4c2ec640ff5..9264443a5ca 100644 --- a/adapters/tappx/tappxtest/params/race/banner.json +++ b/adapters/tappx/tappxtest/params/race/banner.json @@ -1,5 +1,5 @@ { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } \ No newline at end of file diff --git a/adapters/tappx/tappxtest/params/race/video.json b/adapters/tappx/tappxtest/params/race/video.json index 4c2ec640ff5..438543f2362 100644 --- a/adapters/tappx/tappxtest/params/race/video.json +++ b/adapters/tappx/tappxtest/params/race/video.json @@ -1,5 +1,5 @@ { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } \ No newline at end of file diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 288837b7b91..6172da59cbd 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json index c82d0e852f3..12f9c54ae02 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json @@ -11,7 +11,7 @@ "ext": { "bidder": { "host": "example.ho%st.tappx.com", - "endpoint": "PREBIDTEMPLATE", + "endpoint": "ZZ123456PS", "tappxkey": "pub-12345-android-9876" } } @@ -31,7 +31,7 @@ "expectedMakeRequestsErrors": [ { - "value": "Malformed URL: parse (\\\")?https://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"", + "value": "Malformed URL: parse (\\\")?http://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"", "comparison": "regex" } ] diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json index 9a49c8b05a0..85370b4b07c 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json @@ -11,7 +11,7 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "host": "test.tappx.com" + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json index ffe0f14f949..df650dde39a 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json @@ -10,7 +10,7 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", + "endpoint": "ZZ123456PS", "tappxkey": "pub-12345-android-9876" } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json index 2bc147ec07f..4dba6d3b366 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json @@ -10,8 +10,8 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 61e96a442a0..48c1b0fbfa7 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "bidfloor": 1.5 } } @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -49,8 +49,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "bidfloor": 1.5 } } diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index c2db2e1eea8..045695046ff 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index b09ee26b68e..0cb5fd4a400 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json index 1cbd3eefda1..1404204eaf1 100644 --- a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json +++ b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json @@ -11,8 +11,8 @@ "ext": { "bidder": { "tappxkey": 1, - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/usersync.go b/adapters/tappx/usersync.go new file mode 100644 index 00000000000..7bf6b9a6094 --- /dev/null +++ b/adapters/tappx/usersync.go @@ -0,0 +1,12 @@ +package tappx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTappxSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("tappx", 628, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/tappx/usersync_test.go b/adapters/tappx/usersync_test.go new file mode 100644 index 00000000000..65919b9f1f1 --- /dev/null +++ b/adapters/tappx/usersync_test.go @@ -0,0 +1,35 @@ +package tappx + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTappxSyncer(t *testing.T) { + syncURL := "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTappxSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "A", + }, + CCPA: ccpa.Policy{ + Consent: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin=1&gdpr_consent=A&us_privacy=1YNN&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%7B%7BTPPXUID%7D%7D", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.EqualValues(t, 628, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go old mode 100755 new mode 100644 index ae1f62e90b4..755c8fc95c8 --- a/config/config.go +++ b/config/config.go @@ -629,7 +629,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") - // openrtb_ext.BidderTappx doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTappx, "https://ssp.api.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -879,7 +879,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") - v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.tappx.endpoint", "http://{{.Host}}") v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml index 4c8d1560f27..527959b75d1 100644 --- a/static/bidder-info/tappx.yaml +++ b/static/bidder-info/tappx.yaml @@ -5,3 +5,7 @@ capabilities: mediaTypes: - banner - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go old mode 100755 new mode 100644 index c41f7c6c746..25b37fa388c --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -69,6 +69,7 @@ import ( "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/tappx" "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" @@ -156,6 +157,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 73f0e8861c5..818303b8e53 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -78,6 +78,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, + string(openrtb_ext.BidderTappx): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, @@ -112,7 +113,6 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderRevcontent: true, openrtb_ext.BidderSilverMob: true, openrtb_ext.BidderSmaato: true, - openrtb_ext.BidderTappx: true, openrtb_ext.BidderYeahmobi: true, } From 56dfc60d99d11ce602147bcce6a120b77b564937 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Thu, 11 Feb 2021 11:00:00 -0500 Subject: [PATCH 317/603] Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment --- .gitignore | 1 + adapters/beachfront/beachfront.go | 95 +++++++------- .../beachfronttest/exemplary/adm-video.json | 123 ++++++++++++++++++ .../beachfronttest/exemplary/banner.json | 2 +- .../adm-video-by-explicit.json} | 0 .../adm-video-no-ip.json} | 3 - .../banner-and-adm-video-by-explicit.json} | 11 +- .../banner-and-adm-video.json} | 11 +- .../banner-and-nurl-video.json | 5 +- .../supplemental/banner-bad-request-400.json | 76 +++++++++++ .../supplemental/banner-empty_array-200.json | 11 +- .../supplemental/banner-no-appid.json | 34 +++++ .../supplemental/banner-wrong-appids.json | 36 +++++ ...er_response_unmarshal_error_adm_video.json | 80 ++++++++++++ ...idder_response_unmarshal_error_banner.json | 77 +++++++++++ ...r_response_unmarshal_error_nurl_video.json | 82 ++++++++++++ .../supplemental/bidfloor-below-min.json | 96 ++++++++++++++ .../internal-server-error-500.json | 78 +++++++++++ .../beachfronttest/supplemental/no-imps.json | 16 +++ ...dm-nurl--PASS.json => six-nine-combo.json} | 23 +++- .../supplemental/two-four-combo.json | 2 +- .../supplemental/video-no-appid.json | 38 ++++++ .../supplemental/video-wrong-appids.json | 39 ++++++ 23 files changed, 877 insertions(+), 62 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video.json rename adapters/beachfront/beachfronttest/{exemplary/adm-video-by-explicit-type.json => supplemental/adm-video-by-explicit.json} (100%) rename adapters/beachfront/beachfronttest/{exemplary/adm-video-by-default.json => supplemental/adm-video-no-ip.json} (98%) rename adapters/beachfront/beachfronttest/{exemplary/banner-and-adm-video-by-explicit-type.json => supplemental/banner-and-adm-video-by-explicit.json} (95%) rename adapters/beachfront/beachfronttest/{exemplary/banner-and-adm-video-by-default.json => supplemental/banner-and-adm-video.json} (95%) rename adapters/beachfront/beachfronttest/{exemplary => supplemental}/banner-and-nurl-video.json (97%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/internal-server-error-500.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/no-imps.json rename adapters/beachfront/beachfronttest/supplemental/{six-nine-combo--response-order--banner-adm-nurl--PASS.json => six-nine-combo.json} (95%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/video-no-appid.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/video-wrong-appids.json diff --git a/.gitignore b/.gitignore index 79076f9be84..514e217b46c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ vendor prebid-server build debug +__debug_bin # config files pbs.* diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 1f81eda03de..3a431c9cf3a 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "reflect" "strconv" "strings" @@ -24,12 +23,13 @@ const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id" const nurlVideoEndpointSuffix = "&prebidserver" const beachfrontAdapterName = "BF_PREBID_S2S" -const beachfrontAdapterVersion = "0.9.1" +const beachfrontAdapterVersion = "0.9.2" const minBidFloor = 0.01 -const DefaultVideoWidth = 300 -const DefaultVideoHeight = 250 +const defaultVideoWidth = 300 +const defaultVideoHeight = 250 +const fakeIP = "255.255.255.255" type BeachfrontAdapter struct { bannerEndpoint string @@ -249,16 +249,12 @@ func getAppId(ext openrtb_ext.ExtImpBeachfront, media openrtb_ext.BidType) (stri var appid string var error error - if fmt.Sprintf("%s", reflect.TypeOf(ext.AppId)) == "string" && - ext.AppId != "" { - + if ext.AppId != "" { appid = ext.AppId - } else if fmt.Sprintf("%s", reflect.TypeOf(ext.AppIds)) == "openrtb_ext.ExtImpBeachfrontAppIds" { - if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" { - appid = ext.AppIds.Video - } else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" { - appid = ext.AppIds.Banner - } + } else if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" { + appid = ext.AppIds.Video + } else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" { + appid = ext.AppIds.Banner } else { error = errors.New("unable to determine the appId(s) from the supplied extension") } @@ -317,7 +313,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e } if request.Device != nil { - bfr.IP = getIP(request.Device.IP) + bfr.IP = request.Device.IP bfr.DeviceModel = request.Device.Model bfr.DeviceOs = request.Device.OS if request.Device.DNT != nil { @@ -399,6 +395,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } appid, err := getAppId(beachfrontExt, openrtb_ext.BidTypeVideo) + bfReqs[i].AppId = appid if err != nil { // Failed to get an appid, so this request is junk. @@ -407,20 +404,25 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] continue } - bfReqs[i].AppId = appid + bfReqs[i].Request = *request + var secure int8 + + if bfReqs[i].Request.Device == nil { + bfReqs[i].Request.Device = &openrtb.Device{} + } - if beachfrontExt.VideoResponseType != "" { - bfReqs[i].VideoResponseType = beachfrontExt.VideoResponseType + if beachfrontExt.VideoResponseType == "nurl" { + bfReqs[i].VideoResponseType = "nurl" } else { bfReqs[i].VideoResponseType = "adm" - } - bfReqs[i].Request = *request - var secure int8 + if bfReqs[i].Request.Device.IP == "" { + bfReqs[i].Request.Device.IP = fakeIP + } + } if bfReqs[i].Request.Site != nil && bfReqs[i].Request.Site.Domain == "" && bfReqs[i].Request.Site.Page != "" { bfReqs[i].Request.Site.Domain = getDomain(bfReqs[i].Request.Site.Page) - secure = isSecure(bfReqs[i].Request.Site.Page) } @@ -433,7 +435,6 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) } } - } if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.DeviceType == 0 { @@ -454,8 +455,8 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } if imp.Video.H == 0 && imp.Video.W == 0 { - imp.Video.W = DefaultVideoWidth - imp.Video.H = DefaultVideoHeight + imp.Video.W = defaultVideoWidth + imp.Video.H = defaultVideoHeight } if len(bfReqs[i].Request.Cur) == 0 { @@ -467,9 +468,6 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] bfReqs[i].Request.Imp = make([]openrtb.Imp, 1, 1) bfReqs[i].Request.Imp[0] = imp - if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.IP != "" { - bfReqs[i].Request.Device.IP = getIP(bfReqs[i].Request.Device.IP) - } } // Strip out any failed requests @@ -484,15 +482,24 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var bids []openrtb.Bid - var errs []error + // The case of response status == 200 and response body length == 2 below covers the case of the banner endpoint returning + // an empty JSON array ('[]'), which is functionally no content. if response.StatusCode == http.StatusNoContent || (response.StatusCode == http.StatusOK && len(response.Body) <= 2) { - return nil, nil + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("no content or truncated content received from server. status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), + }} + } + + if response.StatusCode >= http.StatusInternalServerError { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("server error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), + }} } - if response.StatusCode == http.StatusBadRequest { + if response.StatusCode >= http.StatusBadRequest { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("bad request status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), + Message: fmt.Sprintf("request error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), }} } @@ -501,6 +508,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern } var xtrnal openrtb.BidRequest + var errs = make([]error, 0) // For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will // only get the User struct and everything else will be nil @@ -550,18 +558,15 @@ func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) op func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { var beachfrontResp []beachfrontResponseSlot - var errs = make([]error, 0) var openrtbResp openrtb.BidResponse - // try it as a video - if err := json.Unmarshal(response.Body, &openrtbResp); err != nil { - errs = append(errs, err) + if err := json.Unmarshal(response.Body, &openrtbResp); err != nil || len(openrtbResp.SeatBid) == 0 { - // try it as a banner if err := json.Unmarshal(response.Body, &beachfrontResp); err != nil { - errs = append(errs, err) - return nil, errs + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprint("server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info"), + }} } else { return postprocessBanner(beachfrontResp, id) } @@ -669,18 +674,12 @@ func isSecure(page string) int8 { } -func getIP(ip string) string { - // This will only effect testing. The backend will return "" for localhost IPs, - // and seems not to know what IPv6 is, so just setting it to one that is not likely to - // be used. - if ip == "" || ip == "::1" || ip == "127.0.0.1" { - return "192.168.255.255" +func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest { + if len(slice) >= s+1 { + return append(slice[:s], slice[s+1:]...) } - return ip -} -func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest { - return append(slice[:s], slice[s+1:]...) + return []beachfrontVideoRequest{} } // Builder builds a new instance of the Beachfront adapter for the given bidder with the given config. diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json new file mode 100644 index 00000000000..26b050f263d --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json index 1eb17286e9c..e3dcffa3d08 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/banner.json @@ -56,7 +56,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "user": {} } }, diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json similarity index 100% rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json rename to adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json similarity index 98% rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json rename to adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json index d3fa41a23c5..842e0f3777e 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json @@ -22,9 +22,6 @@ ], "site": { "page": "https://some.domain.us/some/page.html" - }, - "device":{ - "ip":"255.255.255.255" } }, diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json similarity index 95% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json index 3482f4ef81e..c326a33a642 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json @@ -34,6 +34,9 @@ ], "site": { "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" } }, @@ -64,11 +67,11 @@ "deviceModel": "", "isMobile": 0, "ua": "", - "ip": "", + "ip": "255.255.255.255", "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -109,6 +112,10 @@ "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html" }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json similarity index 95% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json index bff1b76a688..f67903d0866 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json @@ -33,6 +33,9 @@ ], "site": { "page": "https://some.domain.us/some/page.html" + }, + "device": { + "ip": "255.255.255.255" } }, @@ -63,11 +66,11 @@ "deviceModel": "", "isMobile": 0, "ua": "", - "ip": "", + "ip": "255.255.255.255", "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -108,6 +111,10 @@ "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html" }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json similarity index 97% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json index 9db714f41a1..b950d73400b 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json @@ -68,7 +68,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -110,6 +110,9 @@ "page": "https://some.domain.us/some/page.html" }, "isPrebid": true, + "device": { + "devicetype": 2 + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json new file mode 100644 index 00000000000..1771f393361 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 5.02, + "appId": "dudAppId" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "dudAppId", + "bidfloor": 5.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 400, + "body": "" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "request error status code 400 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json index c90c1215f9e..8e607f67663 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json @@ -57,7 +57,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "user": { } } @@ -69,5 +69,12 @@ } ], - "expectedBidResponses": [] + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "no content or truncated content received from server. status code 200 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json new file mode 100644 index 00000000000..a53dd8ce1fc --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unable to determine the appId(s) from the supplied extension", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json new file mode 100644 index 00000000000..3b582648bd5 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appIds": { + "video": "theWrongAppId" + } + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unable to determine the appId(s) from the supplied extension", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json new file mode 100644 index 00000000000..51c676f5076 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json new file mode 100644 index 00000000000..b751f763c0d --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "bannerAppId1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "bannerAppId1", + "bidfloor": 0.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json new file mode 100644 index 00000000000..7990b0a1bb1 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1", + "videoResponseType": "nurl" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1&prebidserver", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "isPrebid": true, + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json new file mode 100644 index 00000000000..f5d4e8228c2 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.002, + "appId": "bannerAppId1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "bannerAppId1", + "bidfloor": 0, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 200, + "body": [ + { + "crid": "crid_1", + "price": 2.942808, + "w": 300, + "h": 250, + "slot": "div-gpt-ad-1460505748561-0", + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + diff --git a/adapters/jixie/jixietest/params/race/banner.json b/adapters/jixie/jixietest/params/race/banner.json new file mode 100644 index 00000000000..a0523d34c3f --- /dev/null +++ b/adapters/jixie/jixietest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "unit": "1000008-AbCdEf123400", + "bidfloor": "0.02" +} diff --git a/adapters/jixie/jixietest/params/race/video.json b/adapters/jixie/jixietest/params/race/video.json new file mode 100644 index 00000000000..b6d11e8fc4e --- /dev/null +++ b/adapters/jixie/jixietest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "unit": "1000008-AbCdEf2345", + "bidfloor": "0.03" +} diff --git a/adapters/jixie/jixietest/supplemental/add-accountid.json b/adapters/jixie/jixietest/supplemental/add-accountid.json new file mode 100644 index 00000000000..d7fe7d864a6 --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-accountid.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456", + "accountid": "accountJX1234" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456", + "accountid": "accountJX1234" + } + } + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/jixietest/supplemental/add-extraprop.json b/adapters/jixie/jixietest/supplemental/add-extraprop.json new file mode 100644 index 00000000000..85c55a3620e --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-extraprop.json @@ -0,0 +1,233 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "jxprop1": "abc" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "jxprop1": "def" } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "jxprop1": "abc" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "jxprop1": "def" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/jixietest/supplemental/add-userid.json b/adapters/jixie/jixietest/supplemental/add-userid.json new file mode 100644 index 00000000000..3ed30da4676 --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-userid.json @@ -0,0 +1,237 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + } + ], + "user": { + "buyeruid": "awesome-buyeruid", + "id": "awesome-id" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + }], + "user": { + "buyeruid": "awesome-buyeruid", + "id": "awesome-id" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/params_test.go b/adapters/jixie/params_test.go new file mode 100644 index 00000000000..4bc2e3080c1 --- /dev/null +++ b/adapters/jixie/params_test.go @@ -0,0 +1,57 @@ +package jixie + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderJixie, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected jixie params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderJixie, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"unit": "1000008-AA77BB88CC" }`, + `{"unit": "1000008-AA77BB88CC", "accountid": "9988776655", "jxprop1": "somethingimportant" }`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `[]`, + `true`, + `{}`, + `{"unit":12345678}`, + `{"Unit":"12345678"}`, + `{"Unit": 12345678}`, + `{"AdUnit": "1"}`, + `{"adUnit": 1}`, + `{"unit": ""}`, + `{"unit": "12345678901234567"}`, + `{"unit":"1000008-AA77BB88CC", "accountid", "jxprop1": "somethingimportant" }`, + `{"unit":"1000008-AA77BB88CC", malformed, }`, +} diff --git a/adapters/jixie/usersync.go b/adapters/jixie/usersync.go new file mode 100644 index 00000000000..8c1d5c07374 --- /dev/null +++ b/adapters/jixie/usersync.go @@ -0,0 +1,12 @@ +package jixie + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewJixieSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("jixie", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/jixie/usersync_test.go b/adapters/jixie/usersync_test.go new file mode 100644 index 00000000000..5be5e51433e --- /dev/null +++ b/adapters/jixie/usersync_test.go @@ -0,0 +1,25 @@ +package jixie + +import ( + "github.com/prebid/prebid-server/privacy" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" +) + +func TestJixieSyncer(t *testing.T) { + syncURL := "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewJixieSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://id.jixie.io/api/sync?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25JXUID%25%25", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 0db6ca61880..952249638d6 100644 --- a/config/config.go +++ b/config/config.go @@ -605,6 +605,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") // openrtb_ext.BidderIx doesn't have a good default. // openrtb_ext.BidderInvibes doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") @@ -846,6 +847,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919") + v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}") v.SetDefault("adapters.invibes.endpoint", "https://{{.Host}}/bid/ServerBidAdContent") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1b3e229f413..8d318ee1232 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -50,6 +50,7 @@ import ( "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/krushmedia" "github.com/prebid/prebid-server/adapters/kubient" @@ -155,6 +156,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInvibes: invibes.Builder, openrtb_ext.BidderIx: ix.Builder, + openrtb_ext.BidderJixie: jixie.Builder, openrtb_ext.BidderKidoz: kidoz.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, openrtb_ext.BidderKubient: kubient.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 13ff4b7ff31..1e148f0a9be 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -121,6 +121,7 @@ const ( BidderInMobi BidderName = "inmobi" BidderInvibes BidderName = "invibes" BidderIx BidderName = "ix" + BidderJixie BidderName = "jixie" BidderKidoz BidderName = "kidoz" BidderKrushmedia BidderName = "krushmedia" BidderKubient BidderName = "kubient" @@ -225,6 +226,7 @@ func CoreBidderNames() []BidderName { BidderInMobi, BidderInvibes, BidderIx, + BidderJixie, BidderKidoz, BidderKrushmedia, BidderKubient, diff --git a/openrtb_ext/imp_jixie.go b/openrtb_ext/imp_jixie.go new file mode 100644 index 00000000000..8fa0570c56b --- /dev/null +++ b/openrtb_ext/imp_jixie.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtImpJixie struct { + Unit string `json:"unit"` + AccountId string `json:"accountid,omitempty"` + JxProp1 string `json:"jxprop1,omitempty"` + JxProp2 string `json:"jxprop2,omitempty"` +} diff --git a/static/bidder-info/jixie.yaml b/static/bidder-info/jixie.yaml new file mode 100644 index 00000000000..ac38f313da1 --- /dev/null +++ b/static/bidder-info/jixie.yaml @@ -0,0 +1,8 @@ +maintainer: + email: contact@jixie.io +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/jixie.json b/static/bidder-params/jixie.json new file mode 100644 index 00000000000..d3d294fe481 --- /dev/null +++ b/static/bidder-params/jixie.json @@ -0,0 +1,27 @@ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Jixie Adapter Params", + "description": "A schema which validates params accepted by the Jixie adapter", + "type": "object", + "properties": { + "unit" : { + "type": "string", + "description": "The unit code of an inventory target", + "minLength": 18 + }, + "accountid" : { + "type": "string", + "description": "The accountid of an inventory target" + }, + "jxprop1" : { + "type": "string", + "description": "jxprop1 of an inventory target" + }, + "jxprop2" : { + "type": "string", + "description": "jxprop2 of an inventory target" + } + }, + "required": ["unit"] + } + diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1d31d7d184c..87b87115405 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -44,6 +44,7 @@ import ( "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/krushmedia" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" @@ -133,6 +134,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 8e3b05a6ab8..53a0baf4b28 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -53,6 +53,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderImprovedigital): syncConfig, string(openrtb_ext.BidderInvibes): syncConfig, string(openrtb_ext.BidderIx): syncConfig, + string(openrtb_ext.BidderJixie): syncConfig, string(openrtb_ext.BidderKrushmedia): syncConfig, string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, From e0c1ac51aa6648718bd800eed25e92dd30c65042 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 25 Feb 2021 11:19:20 -0500 Subject: [PATCH 333/603] Fix Regs Nil Condition (#1723) --- adapters/adform/adform.go | 4 +- .../adformtest/supplemental/regs-ext-nil.json | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 adapters/adform/adformtest/supplemental/regs-ext-nil.json diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index e84df5b8cc7..79bac580f0b 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -453,7 +453,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro gdprApplies := "" var extRegs openrtb_ext.ExtRegs - if request.Regs != nil { + if request.Regs != nil && request.Regs.Ext != nil { if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { errors = append(errors, &errortypes.BadInput{ Message: err.Error(), @@ -466,7 +466,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro eids := "" consent := "" - if request.User != nil { + if request.User != nil && request.User.Ext != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { consent = extUser.Consent diff --git a/adapters/adform/adformtest/supplemental/regs-ext-nil.json b/adapters/adform/adformtest/supplemental/regs-ext-nil.json new file mode 100644 index 00000000000..377c48b0445 --- /dev/null +++ b/adapters/adform/adformtest/supplemental/regs-ext-nil.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }], + "regs": {} + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" + }, + "mockResponse": { + "status": 200, + "body": [{ + "response": "banner", + "banner": "", + "win_bid": 0.5, + "win_cur": "USD", + "width": 300, + "height": 250, + "deal_id": null, + "win_crid": "20078830" + }] + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "", + "crid": "20078830", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file From f3893c33f84cb1e0f712e106ba287ad4bb258f8b Mon Sep 17 00:00:00 2001 From: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Date: Thu, 25 Feb 2021 20:41:03 +0200 Subject: [PATCH 334/603] Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox --- adapters/mobfoxpb/mobfoxpb.go | 53 ++++-- adapters/mobfoxpb/mobfoxpb_test.go | 2 +- .../exemplary/simple-banner-direct-route.json | 128 +++++++++++++++ .../exemplary/simple-banner-rtb-route.json | 128 +++++++++++++++ .../mobfoxpbtest/exemplary/simple-banner.json | 132 --------------- .../exemplary/simple-video-direct-route.json | 125 ++++++++++++++ .../exemplary/simple-video-rtb-route.json | 125 ++++++++++++++ .../mobfoxpbtest/exemplary/simple-video.json | 119 -------------- .../simple-web-banner-direct-route.json | 128 +++++++++++++++ .../simple-web-banner-rtb-route.json | 128 +++++++++++++++ .../exemplary/simple-web-banner.json | 130 --------------- .../params/race/banner-direct-route.json | 3 + .../params/race/banner-rtb-route.json | 3 + .../mobfoxpbtest/params/race/banner.json | 3 - .../params/race/video-direct-route.json | 3 + .../params/race/video-rtb-route.json | 3 + .../mobfoxpbtest/params/race/video.json | 3 - .../supplemental/bad-imp-ext.json | 69 ++++---- .../supplemental/bad_response.json | 152 +++++++++--------- .../supplemental/bad_status_code.json | 142 ++++++++-------- .../supplemental/imp_ext_empty_object.json | 63 ++++---- .../supplemental/imp_ext_string.json | 63 ++++---- .../supplemental/missmatch_bid_id.json | 109 +++++++++++++ .../mobfoxpbtest/supplemental/status-204.json | 142 ++++++++-------- .../mobfoxpbtest/supplemental/status-404.json | 152 +++++++++--------- adapters/mobfoxpb/params_test.go | 4 + config/config.go | 2 +- static/bidder-params/mobfoxpb.json | 42 +++-- 28 files changed, 1343 insertions(+), 813 deletions(-) create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 5369c082c52..6982e419cd5 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" @@ -13,6 +14,16 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) +const ( + ROUTE_NATIVE = "o" + ROUTE_RTB = "rtb" + METHOD_NATIVE = "ortb" + METHOD_RTB = "req" + MACROS_ROUTE = "__route__" + MACROS_METHOD = "__method__" + MACROS_KEY = "__key__" +) + type adapter struct { URI string } @@ -28,21 +39,36 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters // MakeRequests create bid request for mobfoxpb demand func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var err error - var tagID string - + var route string + var method string var adapterRequests []*adapters.RequestData + requestURI := a.URI reqCopy := *request imp := request.Imp[0] - tagID, err = jsonparser.GetString(imp.Ext, "bidder", "TagID") - if err != nil { - errs = append(errs, err) + tagID, errTag := jsonparser.GetString(imp.Ext, "bidder", "TagID") + key, errKey := jsonparser.GetString(imp.Ext, "bidder", "key") + if errTag != nil && errKey != nil { + errs = append(errs, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid or non existing key and tagId, atleast one should be present"), + }) return nil, errs } - imp.TagID = tagID + + if key != "" { + route = ROUTE_RTB + method = METHOD_RTB + requestURI = strings.Replace(requestURI, MACROS_KEY, key, 1) + } else if tagID != "" { + method = METHOD_NATIVE + route = ROUTE_NATIVE + } + + requestURI = strings.Replace(requestURI, MACROS_ROUTE, route, 1) + requestURI = strings.Replace(requestURI, MACROS_METHOD, method, 1) + reqCopy.Imp = []openrtb.Imp{imp} - adapterReq, err := a.makeRequest(&reqCopy) + adapterReq, err := a.makeRequest(&reqCopy, requestURI) if err != nil { errs = append(errs, err) } @@ -52,7 +78,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return adapterRequests, errs } -func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *adapter) makeRequest(request *openrtb.BidRequest, requestURI string) (*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) if err != nil { @@ -64,7 +90,7 @@ func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestDat headers.Add("Accept", "application/json") return &adapters.RequestData{ Method: "POST", - Uri: a.URI, + Uri: requestURI, Body: reqJSON, Headers: headers, }, nil @@ -98,11 +124,10 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest if err != nil { errs = append(errs, err) } else { - b := &adapters.TypedBid{ + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, - } - bidResponse.Bids = append(bidResponse.Bids, b) + }) } } } @@ -126,6 +151,6 @@ func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, // This shouldnt happen. Lets handle it just incase by returning an error. return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), } } diff --git a/adapters/mobfoxpb/mobfoxpb_test.go b/adapters/mobfoxpb/mobfoxpb_test.go index 271d30a97af..56ad948bcde 100644 --- a/adapters/mobfoxpb/mobfoxpb_test.go +++ b/adapters/mobfoxpb/mobfoxpb_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderMobfoxpb, config.Adapter{ - Endpoint: "http://example.com/?c=o&m=ortb"}) + Endpoint: "http://example.com/?c=__route__&m=__method__&key=__key__"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json new file mode 100644 index 00000000000..fb6bd260f74 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=6", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json new file mode 100644 index 00000000000..7b38008536d --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json deleted file mode 100644 index b1936661a71..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "6", - "ext": { - "bidder": { - "TagID": "6" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } -}, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "6", - "ext": { - "bidder": { - "TagID": "6" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "banner" - } - } - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "bids":[ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json new file mode 100644 index 00000000000..a949fdb1527 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "key": "7" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=7", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "key": "7" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json new file mode 100644 index 00000000000..a33f0e62fc7 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json deleted file mode 100644 index 6cdcdc5a6cc..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "TagID": "7" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "tagid": "7", - "ext": { - "bidder": { - "TagID": "7" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "00:01:00", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "mobfoxpb" - } - ], - "cur": "USD" - } - } - } - ], - - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "00:01:00", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json new file mode 100644 index 00000000000..d8727226723 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json new file mode 100644 index 00000000000..adbb7173848 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json deleted file mode 100644 index bba728ac1e9..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "8", - "ext": { - "bidder": { - "TagID": "8" - } - } - } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "8", - "ext": { - "bidder": { - "TagID": "8" - } - } - } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 468, - "h": 60, - "ext": { - "prebid": { - "type": "banner" - } - } - } - ], - "seat": "mobfoxpb" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 468, - "h": 60, - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json new file mode 100644 index 00000000000..3ffcd9bf63c --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json @@ -0,0 +1,3 @@ +{ + "TagID": "6" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json new file mode 100644 index 00000000000..3ce815613d1 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json @@ -0,0 +1,3 @@ +{ + "key": "6" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json deleted file mode 100644 index dbdac1ad995..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "6" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json new file mode 100644 index 00000000000..1e42cfc4a05 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json @@ -0,0 +1,3 @@ +{ + "TagID": "7" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json new file mode 100644 index 00000000000..8c4421b65ef --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json @@ -0,0 +1,3 @@ +{ + "key": "7" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json deleted file mode 100644 index 6e2e0b3803b..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "7" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json index ac3dce598eb..6f2b95a8c54 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json @@ -1,42 +1,41 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "mobfoxpb": { + "TagID": "6" + } + } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": { - "mobfoxpb": { - "TagID": "6" - } + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json index 2f834c92be7..efd1ac90d9d 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json @@ -1,87 +1,85 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "device": { "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } } - }, - "mockResponse": { - "status": 200, - "body": "" - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", - "comparison": "literal" - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json index 96d3a649109..60ee36e48e3 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json @@ -1,81 +1,79 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": {} }, - "device": {} - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} } - } + }, + "mockResponse": { + "status": 400, + "body": {} } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": {} } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json index cc56fa25c2c..9179916e922 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json @@ -1,38 +1,37 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": {} } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": {} - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json index 464c9e31e39..45c32a1aa63 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json @@ -1,38 +1,37 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": "" } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": "" - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json new file mode 100644 index 00000000000..28a1b6c72d4 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id-not", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id-not\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json index c1091969991..e69b248d2a1 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json @@ -1,82 +1,80 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "device": { "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } } - }, - "expectedBidResponses": [], - "mockResponse": { - "status": 204, - "body": {} - } - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "expectedBidResponses": [], + "mockResponse": { + "status": 204, + "body": {} + } + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json index d9ef7108017..987b9daf980 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json @@ -1,87 +1,85 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "device": { "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } } - }, - "mockResponse": { - "status": 404, - "body": {} - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/params_test.go b/adapters/mobfoxpb/params_test.go index 59b9ec383c8..799fdcfa61b 100644 --- a/adapters/mobfoxpb/params_test.go +++ b/adapters/mobfoxpb/params_test.go @@ -37,6 +37,7 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"TagID": "6"}`, + `{"key": "1"}`, } var invalidParams = []string{ @@ -44,4 +45,7 @@ var invalidParams = []string{ `{"tagid": "123"}`, `{"TagID": 16}`, `{"TagID": ""}`, + `{"Key": "1"}`, + `{"key": 1}`, + `{"key":""}`, } diff --git a/config/config.go b/config/config.go index 952249638d6..4cc632d1e32 100644 --- a/config/config.go +++ b/config/config.go @@ -860,7 +860,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}") - v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=o&m=ortb") + v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=__route__&m=__method__&key=__key__") v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1") diff --git a/static/bidder-params/mobfoxpb.json b/static/bidder-params/mobfoxpb.json index 0cc7a16c026..40ce83abc8e 100644 --- a/static/bidder-params/mobfoxpb.json +++ b/static/bidder-params/mobfoxpb.json @@ -1,14 +1,30 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Mobfox Adapter Params", - "description": "A schema which validates params accepted by the Mobfox adapter", - "type": "object", - "properties": { - "TagID": { - "type": "string", - "minLength": 1, - "description": "An ID which identifies the mobfox ad tag" - } - }, - "required" : [ "TagID" ] -} + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Mobfox Adapter Params", + "description": "A schema which validates params accepted by the Mobfox adapter", + "type": "object", + "properties": { + "TagID": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the mobfox ad tag" + }, + "key": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the mobfox adexchange partner" + } + }, + "oneOf": [ + { + "required": [ + "TagID" + ] + }, + { + "required": [ + "key" + ] + } + ] +} \ No newline at end of file From dc14698e3c8a129e4a3dbbe148f45f2f81eac091 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 3 Mar 2021 21:15:20 +0300 Subject: [PATCH 335/603] New Adapter: TrustX (#1726) --- adapters/trustx/usersync.go | 12 ++++++++++++ adapters/trustx/usersync_test.go | 30 +++++++++++++++++++++++++++++ config/config.go | 2 ++ exchange/adapter_builders.go | 1 + openrtb_ext/bidders.go | 2 ++ static/bidder-info/trustx.yaml | 11 +++++++++++ static/bidder-params/trustx.json | 13 +++++++++++++ usersync/usersyncers/syncer.go | 2 ++ usersync/usersyncers/syncer_test.go | 1 + 9 files changed, 74 insertions(+) create mode 100644 adapters/trustx/usersync.go create mode 100644 adapters/trustx/usersync_test.go create mode 100644 static/bidder-info/trustx.yaml create mode 100644 static/bidder-params/trustx.json diff --git a/adapters/trustx/usersync.go b/adapters/trustx/usersync.go new file mode 100644 index 00000000000..a617cd716a4 --- /dev/null +++ b/adapters/trustx/usersync.go @@ -0,0 +1,12 @@ +package trustx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTrustXSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("trustx", 686, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/trustx/usersync_test.go b/adapters/trustx/usersync_test.go new file mode 100644 index 00000000000..ced0d21552b --- /dev/null +++ b/adapters/trustx/usersync_test.go @@ -0,0 +1,30 @@ +package trustx + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTrustXSyncer(t *testing.T) { + syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTrustXSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 686, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 4cc632d1e32..d31e0630a59 100644 --- a/config/config.go +++ b/config/config.go @@ -636,6 +636,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -892,6 +893,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20") + v.SetDefault("adapters.trustx.endpoint", "https://grid.bidswitch.net/sp_bid?sp=trustx") v.SetDefault("adapters.ucfunnel.endpoint", "https://pbs.aralego.com/prebid") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 8d318ee1232..b5d1dc7eab9 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -196,6 +196,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderTelaria: telaria.Builder, openrtb_ext.BidderTriplelift: triplelift.Builder, openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, + openrtb_ext.BidderTrustX: grid.Builder, openrtb_ext.BidderUcfunnel: ucfunnel.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1e148f0a9be..20d6041f51c 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -162,6 +162,7 @@ const ( BidderTelaria BidderName = "telaria" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" + BidderTrustX BidderName = "trustx" BidderUcfunnel BidderName = "ucfunnel" BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" @@ -267,6 +268,7 @@ func CoreBidderNames() []BidderName { BidderTelaria, BidderTriplelift, BidderTripleliftNative, + BidderTrustX, BidderUcfunnel, BidderUnruly, BidderValueImpression, diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml new file mode 100644 index 00000000000..9ce52c1116a --- /dev/null +++ b/static/bidder-info/trustx.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "grid-tech@themediagrid.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/trustx.json b/static/bidder-params/trustx.json new file mode 100644 index 00000000000..efedf9de537 --- /dev/null +++ b/static/bidder-params/trustx.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TrustX Adapter Params", + "description": "A schema which validates params accepted by TrustX adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 87b87115405..72159ad6738 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -75,6 +75,7 @@ import ( "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" + "github.com/prebid/prebid-server/adapters/trustx" "github.com/prebid/prebid-server/adapters/ucfunnel" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" @@ -165,6 +166,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTrustX, trustx.NewTrustXSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 53a0baf4b28..e70abe629ff 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -84,6 +84,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderTelaria): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, + string(openrtb_ext.BidderTrustX): syncConfig, string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, From ee2ec39d520fc12daa8e567ad93b6b9cf3d23e10 Mon Sep 17 00:00:00 2001 From: faithnh Date: Thu, 4 Mar 2021 13:07:56 +0900 Subject: [PATCH 336/603] New Adapter: UNICORN (#1719) * add bidder-info, bidder-params for UNICORN * Add adapter --- adapters/unicorn/params_test.go | 61 +++++ adapters/unicorn/unicorn.go | 244 ++++++++++++++++++ adapters/unicorn/unicorn_test.go | 20 ++ .../exemplary/banner-app-no-source.json | 228 ++++++++++++++++ .../exemplary/banner-app-with-ip.json | 236 +++++++++++++++++ .../exemplary/banner-app-with-ipv6.json | 236 +++++++++++++++++ .../exemplary/banner-app-without-ext.json | 212 +++++++++++++++ .../banner-app-without-placementid.json | 231 +++++++++++++++++ .../unicorntest/exemplary/banner-app.json | 232 +++++++++++++++++ .../exemplary/banner-app_with_fpd.json | 244 ++++++++++++++++++ .../exemplary/banner-app_with_no_fpd.json | 238 +++++++++++++++++ .../unicorntest/params/race/banner.json | 6 + .../unicorn/unicorntest/supplemental/204.json | 170 ++++++++++++ .../unicorn/unicorntest/supplemental/400.json | 175 +++++++++++++ .../unicorn/unicorntest/supplemental/500.json | 175 +++++++++++++ .../supplemental/ccpa-is-enabled.json | 80 ++++++ .../supplemental/coppa-is-enabled.json | 78 ++++++ .../supplemental/gdpr-is-enabled.json | 80 ++++++ .../supplemental/no-imp-ext-prebid.json | 67 +++++ .../unicorntest/supplemental/no-imp-ext.json | 62 +++++ .../supplemental/no-storedrequest-imp.json | 69 +++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_unicorn.go | 9 + static/bidder-info/unicorn.yaml | 6 + static/bidder-params/unicorn.json | 25 ++ usersync/usersyncers/syncer_test.go | 1 + 28 files changed, 3191 insertions(+) create mode 100644 adapters/unicorn/params_test.go create mode 100644 adapters/unicorn/unicorn.go create mode 100644 adapters/unicorn/unicorn_test.go create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json create mode 100644 adapters/unicorn/unicorntest/params/race/banner.json create mode 100644 adapters/unicorn/unicorntest/supplemental/204.json create mode 100644 adapters/unicorn/unicorntest/supplemental/400.json create mode 100644 adapters/unicorn/unicorntest/supplemental/500.json create mode 100644 adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-imp-ext.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json create mode 100644 openrtb_ext/imp_unicorn.go create mode 100644 static/bidder-info/unicorn.yaml create mode 100644 static/bidder-params/unicorn.json diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go new file mode 100644 index 00000000000..4a534208e84 --- /dev/null +++ b/adapters/unicorn/params_test.go @@ -0,0 +1,61 @@ +package unicorn + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderUnicorn, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderUnicorn, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }`, + `{ + "accountId": 199578, + "mediaId": "test_media" + }`, +} + +var invalidParams = []string{ + `{}`, + `{ + "accountId": "199578", + "publisherId": "123456", + "mediaId": 12345, + "placementId": 12345 + }`, + `{ + "publisherId": 123456, + "placementId": "test_placement" + }`, +} diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go new file mode 100644 index 00000000000..52441e1a882 --- /dev/null +++ b/adapters/unicorn/unicorn.go @@ -0,0 +1,244 @@ +package unicorn + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// unicornImpExt is imp ext for UNICORN +type unicornImpExt struct { + Context *unicornImpExtContext `json:"context,omitempty"` + Bidder openrtb_ext.ExtImpUnicorn `json:"bidder"` +} + +type unicornImpExtContext struct { + Data interface{} `json:"data,omitempty"` +} + +// unicornExt is ext for UNICORN +type unicornExt struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + AccountID int64 `json:"accountId,omitempty"` +} + +// Builder builds a new instance of the UNICORN adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var extRegs openrtb_ext.ExtRegs + if request.Regs != nil { + if request.Regs.COPPA == 1 { + return nil, []error{&errortypes.BadInput{ + Message: "COPPA is not supported", + }} + } + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { + if extRegs.GDPR != nil && (*extRegs.GDPR == 1) { + return nil, []error{&errortypes.BadInput{ + Message: "GDPR is not supported", + }} + } + if extRegs.USPrivacy != "" { + return nil, []error{&errortypes.BadInput{ + Message: "CCPA is not supported", + }} + } + } + } + + err := modifyImps(request) + if err != nil { + return nil, []error{err} + } + + var modifiableSource openrtb.Source + if request.Source != nil { + modifiableSource = *request.Source + } else { + modifiableSource = openrtb.Source{} + } + modifiableSource.Ext = setSourceExt() + request.Source = &modifiableSource + + request.Ext, err = setExt(request) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + Headers: getHeaders(request), + } + + return []*adapters.RequestData{requestData}, nil +} + +func getHeaders(request *openrtb.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return headers +} + +func modifyImps(request *openrtb.BidRequest) error { + for i := 0; i < len(request.Imp); i++ { + imp := &request.Imp[i] + + var ext unicornImpExt + err := json.Unmarshal(imp.Ext, &ext) + + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error while decoding imp[%d].ext: %s", i, err), + } + } + + if ext.Bidder.PlacementID == "" { + ext.Bidder.PlacementID, err = getStoredRequestImpID(imp) + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error get StoredRequestImpID from imp[%d]: %s", i, err), + } + } + } + + imp.Ext, err = json.Marshal(ext) + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error while encoding imp[%d].ext: %s", i, err), + } + } + + secure := int8(1) + imp.Secure = &secure + imp.TagID = ext.Bidder.PlacementID + } + return nil +} + +func getStoredRequestImpID(imp *openrtb.Imp) (string, error) { + v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") + + if err != nil { + return "", fmt.Errorf("stored request id not found: %s", err) + } + + return v, nil +} + +func setSourceExt() json.RawMessage { + return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) +} + +func setExt(request *openrtb.BidRequest) (json.RawMessage, error) { + accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") + if err != nil { + accountID = 0 + } + var decodedExt *unicornExt + err = json.Unmarshal(request.Ext, &decodedExt) + if err != nil { + decodedExt = &unicornExt{ + Prebid: nil, + } + } + decodedExt.AccountID = accountID + + ext, err := json.Marshal(decodedExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Error while encoding ext, err: %s", err), + } + } + return ext, nil +} + +// MakeBids unpacks the server's response into Bids. +func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected http status code: 400", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected http status code: %d", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + var bidType openrtb_ext.BidType + for _, imp := range request.Imp { + if imp.ID == bid.ImpID { + if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + } + } + } + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go new file mode 100644 index 00000000000..1a0d67d29f7 --- /dev/null +++ b/adapters/unicorn/unicorn_test.go @@ -0,0 +1,20 @@ +package unicorn + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderUnicorn, config.Adapter{ + Endpoint: "https://ds.uncn.jp"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "unicorntest", bidder) +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json new file mode 100644 index 00000000000..c37c2095d48 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json @@ -0,0 +1,228 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + } + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json new file mode 100644 index 00000000000..2d38a990db3 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "ip": "12.1.2.3", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "ip": "12.1.2.3", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json new file mode 100644 index 00000000000..595741a0aa1 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "ipv6": "2400:2410:9120:3400:15f3:8c50:3a0:6820", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "ipv6": "2400:2410:9120:3400:15f3:8c50:3a0:6820", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json new file mode 100644 index 00000000000..8b5423a5556 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json @@ -0,0 +1,212 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 78.3, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 78.3, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json new file mode 100644 index 00000000000..c05d3b6f536 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json @@ -0,0 +1,231 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["USD"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["USD"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_unicorn" + } + }, + "tagid": "test_unicorn", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "USD", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 0.783, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 0.783, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app.json b/adapters/unicorn/unicorntest/exemplary/banner-app.json new file mode 100644 index 00000000000..b29c1e1d625 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app.json @@ -0,0 +1,232 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json new file mode 100644 index 00000000000..0a6fb420adc --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json @@ -0,0 +1,244 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": { + "firstPartyData1": ["firstPartyData1"], + "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + } + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": { + "firstPartyData1": ["firstPartyData1"], + "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + } + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json new file mode 100644 index 00000000000..022246382f8 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json @@ -0,0 +1,238 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": {} + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": {} + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/params/race/banner.json b/adapters/unicorn/unicorntest/params/race/banner.json new file mode 100644 index 00000000000..668983e3d19 --- /dev/null +++ b/adapters/unicorn/unicorntest/params/race/banner.json @@ -0,0 +1,6 @@ +{ + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" +} diff --git a/adapters/unicorn/unicorntest/supplemental/204.json b/adapters/unicorn/unicorntest/supplemental/204.json new file mode 100644 index 00000000000..a05864090ea --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/204.json @@ -0,0 +1,170 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/400.json b/adapters/unicorn/unicorntest/supplemental/400.json new file mode 100644 index 00000000000..6578082ca19 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/400.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 400", + "comparison": "literal" + } + ] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/500.json b/adapters/unicorn/unicorntest/supplemental/500.json new file mode 100644 index 00000000000..c0811be4b24 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/500.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 500", + "comparison": "literal" + } + ] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json new file mode 100644 index 00000000000..6bc396c67f1 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "ext": { + "us_privacy": "1YNN" + } + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "CCPA is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json new file mode 100644 index 00000000000..1c33ce2e805 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "coppa": 1 + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "COPPA is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json new file mode 100644 index 00000000000..3c9222d8cc2 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "GDPR is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json new file mode 100644 index 00000000000..2e6ce79a176 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors" : [{ + "value": "Error get StoredRequestImpID from imp[0]: stored request id not found: Key path not found", + "comparison": "literal" + }] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json new file mode 100644 index 00000000000..bab6e8d9603 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error while decoding imp[0].ext: unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json new file mode 100644 index 00000000000..d903effd466 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors" : [{ + "value": "Error get StoredRequestImpID from imp[0]: stored request id not found: Key path not found", + "comparison": "literal" + }] +} diff --git a/config/config.go b/config/config.go index d31e0630a59..2abdd117d52 100644 --- a/config/config.go +++ b/config/config.go @@ -638,6 +638,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") + // openrtb_ext.BidderUnicorn doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") @@ -895,6 +896,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20") v.SetDefault("adapters.trustx.endpoint", "https://grid.bidswitch.net/sp_bid?sp=trustx") v.SetDefault("adapters.ucfunnel.endpoint", "https://pbs.aralego.com/prebid") + v.SetDefault("adapters.unicorn.endpoint", "https://ds.uncn.jp/pb/0/bid.json") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index b5d1dc7eab9..8d0c0a4056c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -90,6 +90,7 @@ import ( "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" + "github.com/prebid/prebid-server/adapters/unicorn" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" "github.com/prebid/prebid-server/adapters/verizonmedia" @@ -198,6 +199,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, openrtb_ext.BidderTrustX: grid.Builder, openrtb_ext.BidderUcfunnel: ucfunnel.Builder, + openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 20d6041f51c..600e130b167 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -164,6 +164,7 @@ const ( BidderTripleliftNative BidderName = "triplelift_native" BidderTrustX BidderName = "trustx" BidderUcfunnel BidderName = "ucfunnel" + BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" BidderVerizonMedia BidderName = "verizonmedia" @@ -270,6 +271,7 @@ func CoreBidderNames() []BidderName { BidderTripleliftNative, BidderTrustX, BidderUcfunnel, + BidderUnicorn, BidderUnruly, BidderValueImpression, BidderVerizonMedia, diff --git a/openrtb_ext/imp_unicorn.go b/openrtb_ext/imp_unicorn.go new file mode 100644 index 00000000000..ad75414caa5 --- /dev/null +++ b/openrtb_ext/imp_unicorn.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpUnicorn defines the contract for bidrequest.imp[i].ext.unicorn +type ExtImpUnicorn struct { + PlacementID string `json:"placementId,omitempty"` + PublisherID int `json:"publisherId,omitempty"` + MediaID string `json:"mediaId"` + AccountID int `json:"accountId"` +} diff --git a/static/bidder-info/unicorn.yaml b/static/bidder-info/unicorn.yaml new file mode 100644 index 00000000000..f1b5a4e7f3e --- /dev/null +++ b/static/bidder-info/unicorn.yaml @@ -0,0 +1,6 @@ +maintainer: + email: prebid@unicorn.inc +capabilities: + app: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-params/unicorn.json b/static/bidder-params/unicorn.json new file mode 100644 index 00000000000..f9c4e1677b6 --- /dev/null +++ b/static/bidder-params/unicorn.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "UNICORN Adapter Params", + "description": "A schema which validates params accepted by the UNICORN adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "description": "In Application, if placementId is empty, prebid server configuration id will be used as placementId." + }, + "publisherId": { + "type": "integer", + "description": "Account specific publisher id" + }, + "mediaId": { + "type": "string", + "description": "Publisher specific media id" + }, + "accountId": { + "type": "integer", + "description": "Account ID for charge request" + } + }, + "required" : ["mediaId", "accountId"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index e70abe629ff..79530c867f7 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -118,6 +118,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderRevcontent: true, openrtb_ext.BidderSilverMob: true, openrtb_ext.BidderSmaato: true, + openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, } From 87277e14e554f185627ddd5b1d5202be69409a2a Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 4 Mar 2021 14:32:48 -0500 Subject: [PATCH 337/603] Fixes GDPR bug about being overly strict on publisher restrictions (#1730) --- gdpr/impl.go | 10 ++++++---- gdpr/impl_test.go | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index 06b625da95c..5b1349ffe60 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -201,15 +201,17 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { return false } + + purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) + legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { - return vendor.PurposeStrict(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) + return purposeAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireLegitInterest, vendorID) { // Need LITransparency here - return vendor.LegitimateInterestStrict(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + return legitInterest } - purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) - legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) return purposeAllowed || legitInterest } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 45d2ba43ce3..737ed14a300 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -317,6 +317,12 @@ func buildTCF2VendorList34() tcf2VendorList { Purposes: []int{2, 4, 7}, SpecialPurposes: []int{1}, }, + "20": { + ID: 20, + Purposes: []int{1}, + LegIntPurposes: []int{2, 7}, + FlexiblePurposes: []int{2, 7}, + }, "32": { ID: 32, Purposes: []int{1, 2, 4, 7}, @@ -353,11 +359,13 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), + 74: parseVendorListDataV2(t, vendorListData), }), }, } @@ -389,6 +397,17 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { allowGeo: false, allowID: true, }, + { + // This requires publisher restrictions on any claimed purposes, 2-10. Vendor must declare all claimed purposes + // as flex with legit interest as primary. + // Using vendor 20 for this. + description: "OpenX vendor test, Specific purposes/LIs claimed, no geo claimed, Publisher restrictions apply", + bidder: openrtb_ext.BidderOpenx, + consent: "CPAavcCPAavcCAGABCFRBKCsAP_AAH_AAAqIHFNf_X_fb3_j-_59_9t0eY1f9_7_v-0zjgeds-8Nyd_X_L8X5mM7vB36pq4KuR4Eu3LBAQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XT_ZKY79_____7__-_____7_f__-__3_vp9V---wOJAIMBAUAgAEMAAQIFCIQAAQhiQAAAABBCIBQJIAEqgAWVwEdoIEACAxAQgQAgBBQgwCAAQAAJKAgBACwQCAAiAQAAgAEAIAAEIAILACQEAAAEAJCAAiACECAgiAAg5DAgIgCCAFABAAAuJDACAMooASBAPGQGAAKAAqACGAEwALgAjgBlgDUAHZAPsA_ACMAFLAK2AbwBMQCbAFogLYAYEAw8BkQDOQGeAM-EQHwAVABWAC4AIYAZAAywBqADZAHYAPwAgABGAClgFPANYAdUA-QCGwEOgIvASIAmwBOwCkQFyAMCAYSAw8Bk4DOQGfCQAYADgBzgN_CQTgAEAALgAoACoAGQAOAAeABAACIAFQAMIAaABqADyAIYAigBMgCqAKwAWAAuABvADmAHoAQ0AiACJgEsAS4AmgBSgC3AGGAMgAZcA1ADVAGyAO8AewA-IB9gH6AQAAjABQQClgFPAL8AYoA1gBtADcAG8AOIAegA-QCGwEOgIqAReAkQBMQCZQE2AJ2AUOApEBYoC2AFyALvAYEAwYBhIDDQGHgMiAZIAycBlwDOQGfANIAadA1gDWQoAEAYQaBIACoAKwAXABDADIAGWANQAbIA7AB-AEAAIKARgApYBT4C0ALSAawA3gB1QD5AIbAQ6Ai8BIgCbAE7AKRAXIAwIBhIDDwGMAMnAZyAzwBnwcAEAA4Bv4qA2ABQAFQAQwAmABcAEcAMsAagA7AB-AEYAKXAWgBaQDeAJBATEAmwBTYC2AFyAMCAYeAyIBnIDPAGfANyHQWQAFwAUABUADIAHAAQAAiABdADAAMYAaABqADwAH0AQwBFACZAFUAVgAsABcADEAGYAN4AcwA9ACGAERAJYAmABNACjAFKALEAW4AwwBkADKAGiANQAbIA3wB3gD2gH2AfoBGACVAFBAKeAWKAtAC0gFzALyAX4AxQBuADiQHTAdQA9ACGwEOgIiAReAkEBIgCbAE7AKHAU0AqwBYsC2ALZAXAAuQBdoC7wGEgMNAYeAxIBjADHgGSAMnAZUAywBlwDOQGfANEgaQBpIDSwGnANYAbGPABAIqAb-QgZgALAAoABkAEQALgAYgBDACYAFUALgAYgAzABvAD0AI4AWIAygBqADfAHfAPsA_ACMAFBAKGAU-AtAC0gF-AMUAdQA9ACQQEiAJsAU0AsUBaMC2ALaAXAAuQBdoDDwGJAMiAZOAzkBngDPgGiANJAaWA4AlAyAAQAAsACgAGQAOAAigBgAGIAPAAiABMACqAFwAMQAZgA2gCGgEQARIAowBSgC3AGEAMoAaoA2QB3gD8AIwAU-AtAC0gGKANwAcQA6gCHQEXgJEATYAsUBbAC7QGHgMiAZOAywBnIDPAGfANIAawA4AmACARUA38pBBAAXABQAFQAMgAcABAACKAGAAYwA0ADUAHkAQwBFACYAFIAKoAWAAuABiADMAHMAQwAiABRgClAFiALcAZQA0QBqgDZAHfAPsA_ACMAFBAKGAVsAuYBeQDaAG4APQAh0BF4CRAE2AJ2AUOApoBWwCxQFsALgAXIAu0BhoDDwGMAMiAZIAycBlwDOQGeAM-gaQBpMDWANZAbGVABAA-Ab-A.YAAAAAAAAAAA", + allowPI: true, + allowGeo: false, + allowID: true, + }, } for _, td := range testDefs { From a3cc810b3b8f704aad9f292242a0c3e81e5f7999 Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Fri, 5 Mar 2021 16:15:17 -0500 Subject: [PATCH 338/603] 33Across: Updated exchange endpoint (#1738) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 2abdd117d52..2daba1654fb 100644 --- a/config/config.go +++ b/config/config.go @@ -797,7 +797,7 @@ func SetupViper(v *viper.Viper, filename string) { // Disabling adapters by default that require some specific config params. // If you're using one of these, make sure you check out the documentation (https://github.com/prebid/prebid-server/tree/master/docs/bidders) // for them and specify all the parameters they need for them to work correctly. - v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") + v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") From c15c66c4f3a8717688382ef4df3bf23ec94b4865 Mon Sep 17 00:00:00 2001 From: guiann Date: Sun, 7 Mar 2021 06:48:31 +0100 Subject: [PATCH 339/603] New Adapter: Adyoulike (#1700) Co-authored-by: Damien Dumas --- adapters/adyoulike/adyoulike.go | 137 ++++++++++++++ adapters/adyoulike/adyoulike_test.go | 22 +++ .../exemplary/multiformat-impression.json | 169 ++++++++++++++++++ .../adyouliketest/params/race/banner.json | 3 + .../adyouliketest/params/race/video.json | 3 + .../supplemental/invalid-bid-response.json | 65 +++++++ .../supplemental/status-bad-request.json | 66 +++++++ .../supplemental/status-no-content.json | 66 +++++++ .../status-service-unavailable.json | 66 +++++++ .../supplemental/status-unknown.json | 66 +++++++ adapters/adyoulike/params_test.go | 62 +++++++ adapters/adyoulike/usersync.go | 12 ++ adapters/adyoulike/usersync_test.go | 36 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adyoulike.go | 18 ++ static/bidder-info/adyoulike.yaml | 9 + static/bidder-params/adyoulike.json | 33 ++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 21 files changed, 842 insertions(+) create mode 100644 adapters/adyoulike/adyoulike.go create mode 100644 adapters/adyoulike/adyoulike_test.go create mode 100644 adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json create mode 100644 adapters/adyoulike/adyouliketest/params/race/banner.json create mode 100644 adapters/adyoulike/adyouliketest/params/race/video.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-no-content.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-unknown.json create mode 100644 adapters/adyoulike/params_test.go create mode 100644 adapters/adyoulike/usersync.go create mode 100644 adapters/adyoulike/usersync_test.go create mode 100644 openrtb_ext/imp_adyoulike.go create mode 100644 static/bidder-info/adyoulike.yaml create mode 100644 static/bidder-params/adyoulike.json diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go new file mode 100644 index 00000000000..9a137a41fef --- /dev/null +++ b/adapters/adyoulike/adyoulike.go @@ -0,0 +1,137 @@ +package adyoulike + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" +) + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + return &adapter{ + endpoint: config.Endpoint, + }, nil +} + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + var err error + var tagID string + + reqCopy := *openRTBRequest + reqCopy.Imp = []openrtb.Imp{} + for ind, imp := range openRTBRequest.Imp { + reqCopy.Imp = append(reqCopy.Imp, imp) + + tagID, err = jsonparser.GetString(reqCopy.Imp[ind].Ext, "bidder", "placement") + if err != nil { + errs = append(errs, err) + continue + } + + reqCopy.Imp[ind].TagID = tagID + } + + openRTBRequestJSON, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + } + + if len(errs) > 0 { + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +const unexpectedStatusCodeFormat = "" + + "Unexpected status code: %d. Run with request.debug = 1 for more info" + +func (a *adapter) MakeBids( + openRTBRequest *openrtb.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + switch bidderRawResponse.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, []error{errors.New("MakeBids error: No Content")} + case http.StatusBadRequest: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRTBRequest.Imp)) + bidResponse.Currency = openRTBBidderResponse.Cur + for _, seatBid := range openRTBBidderResponse.SeatBid { + for idx := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[idx], + BidType: getMediaTypeForImp(seatBid.Bid[idx].ImpID, openRTBRequest.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +// getMediaTypeForBid determines which type of bid. +func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else if imp.Banner == nil && imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } + } + } + + return mediaType +} diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go new file mode 100644 index 00000000000..9ab689f3c77 --- /dev/null +++ b/adapters/adyoulike/adyoulike_test.go @@ -0,0 +1,22 @@ +package adyoulike + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsBidderEndpoint = "https://localhost/bid/4" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdyoulike, config.Adapter{ + Endpoint: testsBidderEndpoint}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adyouliketest", bidder) +} diff --git a/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json new file mode 100644 index 00000000000..2ae99c04969 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + }, + { + "id": "video-imp-id", + "video": { + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placement": "54321" + } + } + }, + { + "id": "native-imp-id", + "native": { + "title": "required" + }, + "ext": { + "bidder": { + "placement": "123123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + }, + { + "ext": { + "bidder": { + "placement": "54321" + } + }, + "id": "video-imp-id", + "tagid": "54321", + "video": { + "h": 480, + "mimes": null, + "w": 640 + } + }, + { + "ext": { + "bidder": { + "placement": "123123" + } + }, + "id": "native-imp-id", + "native": { + "request": "" + }, + "tagid": "123123" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "12340", + "impid": "banner-imp-id", + "price": 300, + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2Fhtml%3E", + "nurl": "http://example.com/winnoticeurl0" + }, + { + "id": "12341", + "impid": "video-imp-id", + "price": 301, + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2FVAST%3E", + "nurl": "http://example.com/winnoticeurl1" + }, + { + "id": "12342", + "impid": "native-imp-id", + "price": 302, + "adm": "{'json':'response','for':'native'}", + "nurl": "http://example.com/winnoticeurl2" + } + ] + }] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "12340", + "impid": "banner-imp-id", + "price": 300, + "nurl": "http://example.com/winnoticeurl0", + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2Fhtml%3E" + }, + "type": "banner" + }, + { + "bid": { + "id": "12341", + "impid": "video-imp-id", + "price": 301, + "nurl": "http://example.com/winnoticeurl1", + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2FVAST%3E" + }, + "type": "video" + }, + { + "bid": { + "id": "12342", + "impid": "native-imp-id", + "price": 302, + "nurl": "http://example.com/winnoticeurl2", + "adm": "{'json':'response','for':'native'}" + }, + "type": "native" + } + ] + } + ] + } diff --git a/adapters/adyoulike/adyouliketest/params/race/banner.json b/adapters/adyoulike/adyouliketest/params/race/banner.json new file mode 100644 index 00000000000..726ca878c05 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "placement": "19f1b372c7548ec1fe734d2c9f8dc688" +} diff --git a/adapters/adyoulike/adyouliketest/params/race/video.json b/adapters/adyoulike/adyouliketest/params/race/video.json new file mode 100644 index 00000000000..d0883f5e04a --- /dev/null +++ b/adapters/adyoulike/adyouliketest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "placement": "19f1b372c7548ec1fe734d2c9f8dc688" +} diff --git a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json new file mode 100644 index 00000000000..b156dd65ae3 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..83b5f3611d8 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json new file mode 100644 index 00000000000..9ef51e88a41 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "MakeBids error: No Content", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json new file mode 100644 index 00000000000..2b5872a16f5 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 503, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json new file mode 100644 index 00000000000..0b9ce745dd9 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 999, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 999. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go new file mode 100644 index 00000000000..b57264b3dbd --- /dev/null +++ b/adapters/adyoulike/params_test.go @@ -0,0 +1,62 @@ +package adyoulike + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adyoulike.json +// +// These also validate the format of the external API: request.imp[i].ext.adyoulike + +// TestValidParams makes sure that the adyoulike schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdyoulike, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adyoulike params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adyoulike schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdyoulike, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placement":"123"}`, + `{"placement":"123","campaign":"456"}`, + `{"placement":"123","campaign":"456","track":"789"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC","source":"SSP"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC","source":"SSP","debug":"info"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"invalid":"123"}`, + `{"placement":123}`, + `{"placement":"123","campaign":123}`, +} diff --git a/adapters/adyoulike/usersync.go b/adapters/adyoulike/usersync.go new file mode 100644 index 00000000000..0ecc4e405dc --- /dev/null +++ b/adapters/adyoulike/usersync.go @@ -0,0 +1,12 @@ +package adyoulike + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdyoulikeSyncer(urlTemplate *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adyoulike", 259, urlTemplate, adapters.SyncTypeRedirect) +} diff --git a/adapters/adyoulike/usersync_test.go b/adapters/adyoulike/usersync_test.go new file mode 100644 index 00000000000..5090acb1a14 --- /dev/null +++ b/adapters/adyoulike/usersync_test.go @@ -0,0 +1,36 @@ +package adyoulike + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" +) + +func TestAdyoulikeSyncer(t *testing.T) { + syncURL := "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string={{.GDPRConsent}}&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdyoulikeSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + }, + CCPA: ccpa.Policy{ + Consent: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&gdpr=1&us_privacy=1-YY", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 259, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 2daba1654fb..22ce8faf0b6 100644 --- a/config/config.go +++ b/config/config.go @@ -577,6 +577,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdyoulike, "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadyoulike%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BBUYER_USERID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAMX, "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Damx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -815,6 +816,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.adyoulike.endpoint", "https://broker-preprod.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 8d0c0a4056c..46c967065cb 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/applogy" @@ -126,6 +127,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdyoulike: adyoulike.Builder, openrtb_ext.BidderAJA: aja.Builder, openrtb_ext.BidderAMX: amx.Builder, openrtb_ext.BidderApplogy: applogy.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 600e130b167..570701603fd 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -90,6 +90,7 @@ const ( BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" + BidderAdyoulike BidderName = "adyoulike" BidderAJA BidderName = "aja" BidderAMX BidderName = "amx" BidderApplogy BidderName = "applogy" @@ -197,6 +198,7 @@ func CoreBidderNames() []BidderName { BidderAdtarget, BidderAdtelligent, BidderAdvangelists, + BidderAdyoulike, BidderAJA, BidderAMX, BidderApplogy, diff --git a/openrtb_ext/imp_adyoulike.go b/openrtb_ext/imp_adyoulike.go new file mode 100644 index 00000000000..67a94123734 --- /dev/null +++ b/openrtb_ext/imp_adyoulike.go @@ -0,0 +1,18 @@ +package openrtb_ext + +// ExtImpAdyoulike defines the contract for bidrequest.imp[i].ext.adyoulike +type ExtImpAdyoulike struct { + // placementId, only mandatory field + PlacementId string `json:"placement"` + + // Id of the forced campaign + Campaign string `json:"campaign"` + // Id of the forced track + Track string `json:"track"` + // Id of the forced creative + Creative string `json:"creative"` + // Context of the campaign values [SSP|AdServer] + Source string `json:"source"` + // Abitrary Id used for debug purpose + Debug string `json:"debug"` +} diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml new file mode 100644 index 00000000000..3f35aa5b038 --- /dev/null +++ b/static/bidder-info/adyoulike.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "core@adyoulike.com" +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/adyoulike.json b/static/bidder-params/adyoulike.json new file mode 100644 index 00000000000..f426a0923d7 --- /dev/null +++ b/static/bidder-params/adyoulike.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdYouLike Adapter Params", + "description": "A schema which validates params accepted by the AdYouLike adapter", + "type": "object", + "properties": { + "placement": { + "type": "string", + "description": "Placement Id" + }, + "campaign": { + "type": "string", + "description": "Id of a forced campaign" + }, + "track": { + "type": "string", + "description": "Id of a forced Track" + }, + "creative": { + "type": "string", + "description": "Id of a forced creative" + }, + "source": { + "type": "string", + "description": "context of the campaign" + }, + "debug": { + "type": "string", + "description": "Abitrary id used for debug purpose" + } + }, + "required": ["placement"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 72159ad6738..7467eb261fa 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -17,6 +17,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/appnexus" @@ -109,6 +110,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtarget, adtarget.NewAdtargetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdyoulike, adyoulike.NewAdyoulikeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAMX, amx.NewAMXSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAppnexus, appnexus.NewAppnexusSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 79530c867f7..9727a2d4e22 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdtarget): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, + string(openrtb_ext.BidderAdyoulike): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, string(openrtb_ext.BidderAMX): syncConfig, string(openrtb_ext.BidderAppnexus): syncConfig, From 5a0251a40c6b24fc587208a1871b7ed6a1c3f49b Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 9 Mar 2021 13:42:52 -0500 Subject: [PATCH 340/603] Hoist GVL ID To Bidder Info (#1721) --- adapters/33across/usersync.go | 2 +- adapters/33across/usersync_test.go | 1 - adapters/acuityads/usersync.go | 2 +- adapters/acuityads/usersync_test.go | 1 - adapters/adform/usersync.go | 2 +- adapters/adform/usersync_test.go | 1 - adapters/adkernel/usersync.go | 4 +-- adapters/adkernel/usersync_test.go | 1 - adapters/adkernelAdn/usersync.go | 4 +-- adapters/adkernelAdn/usersync_test.go | 1 - adapters/adman/usersync.go | 2 +- adapters/adman/usersync_test.go | 1 - adapters/admixer/usersync.go | 5 ++-- adapters/admixer/usersync_test.go | 2 +- adapters/adocean/usersync.go | 2 +- adapters/adocean/usersync_test.go | 1 - adapters/adpone/usersync.go | 2 -- adapters/adpone/usersync_test.go | 1 - adapters/adtarget/usersync.go | 2 +- adapters/adtarget/usersync_test.go | 1 - adapters/adtelligent/usersync.go | 2 +- adapters/adtelligent/usersync_test.go | 1 - adapters/advangelists/usersync.go | 2 +- adapters/advangelists/usersync_test.go | 1 - adapters/adyoulike/usersync.go | 2 +- adapters/adyoulike/usersync_test.go | 1 - adapters/aja/usersync.go | 2 +- adapters/aja/usersync_test.go | 1 - adapters/amx/usersync.go | 2 +- adapters/amx/usersync_test.go | 1 - adapters/appnexus/usersync.go | 2 +- adapters/appnexus/usersync_test.go | 1 - adapters/audienceNetwork/usersync.go | 2 +- adapters/audienceNetwork/usersync_test.go | 1 - adapters/avocet/usersync.go | 2 +- adapters/avocet/usersync_test.go | 1 - adapters/beachfront/usersync.go | 3 -- adapters/beachfront/usersync_test.go | 1 - adapters/beintoo/usersync.go | 2 +- adapters/beintoo/usersync_test.go | 1 - adapters/between/usersync.go | 2 +- adapters/between/usersync_test.go | 6 ++-- adapters/brightroll/usersync.go | 2 +- adapters/brightroll/usersync_test.go | 1 - adapters/colossus/usersync.go | 2 +- adapters/colossus/usersync_test.go | 1 - adapters/connectad/usersync.go | 2 +- adapters/connectad/usersync_test.go | 1 - adapters/consumable/usersync.go | 4 --- adapters/consumable/usersync_test.go | 1 - adapters/conversant/usersync.go | 2 +- adapters/conversant/usersync_test.go | 1 - adapters/cpmstar/usersync.go | 2 +- adapters/cpmstar/usersync_test.go | 1 - adapters/datablocks/usersync.go | 4 +-- adapters/datablocks/usersync_test.go | 1 - adapters/deepintent/usersync.go | 2 +- adapters/deepintent/usersync_test.go | 1 - adapters/dmx/usersync.go | 2 +- adapters/dmx/usersync_test.go | 4 +-- adapters/emx_digital/usersync.go | 2 +- adapters/emx_digital/usersync_test.go | 1 - adapters/engagebdr/usersync.go | 5 ++-- adapters/engagebdr/usersync_test.go | 1 - adapters/eplanning/usersync.go | 2 +- adapters/eplanning/usersync_test.go | 1 - adapters/gamma/usersync.go | 2 +- adapters/gamma/usersync_test.go | 1 - adapters/gamoshi/usersync.go | 2 +- adapters/gamoshi/usersync_test.go | 1 - adapters/grid/usersync.go | 2 +- adapters/grid/usersync_test.go | 1 - adapters/gumgum/usersync.go | 2 +- adapters/gumgum/usersync_test.go | 1 - adapters/improvedigital/usersync.go | 2 +- adapters/improvedigital/usersync_test.go | 1 - adapters/invibes/usersync.go | 1 - adapters/invibes/usersync_test.go | 1 - adapters/ix/usersync.go | 2 +- adapters/ix/usersync_test.go | 1 - adapters/jixie/jixie_test.go | 3 +- adapters/jixie/usersync.go | 2 +- adapters/jixie/usersync_test.go | 3 +- adapters/krushmedia/usersync.go | 2 +- adapters/krushmedia/usersync_test.go | 1 - adapters/lifestreet/usersync.go | 2 +- adapters/lifestreet/usersync_test.go | 1 - adapters/lockerdome/usersync.go | 2 +- adapters/lockerdome/usersync_test.go | 1 - adapters/logicad/usersync.go | 2 +- adapters/logicad/usersync_test.go | 1 - adapters/lunamedia/usersync.go | 2 +- adapters/lunamedia/usersync_test.go | 1 - adapters/marsmedia/usersync.go | 2 +- adapters/marsmedia/usersync_test.go | 1 - adapters/mediafuse/usersync.go | 2 +- adapters/mediafuse/usersync_test.go | 1 - adapters/mgid/usersync.go | 2 +- adapters/mgid/usersync_test.go | 1 - adapters/nanointeractive/usersync.go | 2 +- adapters/nanointeractive/usersync_test.go | 1 - adapters/ninthdecimal/usersync.go | 2 +- adapters/ninthdecimal/usersync_test.go | 1 - adapters/nobid/usersync.go | 2 +- adapters/onetag/usersync.go | 2 +- adapters/onetag/usersync_test.go | 1 - adapters/openx/usersync.go | 2 +- adapters/openx/usersync_test.go | 1 - adapters/pubmatic/usersync.go | 2 +- adapters/pubmatic/usersync_test.go | 1 - adapters/pulsepoint/usersync.go | 2 +- adapters/pulsepoint/usersync_test.go | 1 - adapters/rhythmone/usersync.go | 2 +- adapters/rhythmone/usersync_test.go | 1 - adapters/rtbhouse/usersync.go | 2 -- adapters/rtbhouse/usersync_test.go | 1 - adapters/rubicon/usersync.go | 2 +- adapters/rubicon/usersync_test.go | 1 - adapters/sharethrough/usersync.go | 5 ++-- adapters/sharethrough/usersync_test.go | 1 - adapters/smartadserver/usersync.go | 2 +- adapters/smartadserver/usersync_test.go | 1 - adapters/smartrtb/usersync.go | 2 +- adapters/smartrtb/usersync_test.go | 1 - adapters/smartyads/usersync.go | 2 +- adapters/smartyads/usersync_test.go | 1 - adapters/somoaudience/usersync.go | 2 +- adapters/somoaudience/usersync_test.go | 1 - adapters/sonobi/usersync.go | 5 ++-- adapters/sonobi/usersync_test.go | 1 - adapters/sovrn/usersync.go | 2 +- adapters/sovrn/usersync_test.go | 1 - adapters/synacormedia/usersync.go | 2 +- adapters/synacormedia/usersync_test.go | 1 - adapters/syncer.go | 31 +++++---------------- adapters/tappx/usersync.go | 2 +- adapters/tappx/usersync_test.go | 1 - adapters/telaria/usersync.go | 2 +- adapters/telaria/usersync_test.go | 1 - adapters/triplelift/usersync.go | 2 +- adapters/triplelift/usersync_test.go | 1 - adapters/triplelift_native/usersync.go | 2 +- adapters/triplelift_native/usersync_test.go | 1 - adapters/trustx/usersync.go | 2 +- adapters/trustx/usersync_test.go | 1 - adapters/ucfunnel/usersync.go | 2 +- adapters/ucfunnel/usersync_test.go | 1 - adapters/unruly/usersync.go | 2 +- adapters/unruly/usersync_test.go | 1 - adapters/valueimpression/usersync.go | 2 +- adapters/valueimpression/usersync_test.go | 1 - adapters/verizonmedia/usersync.go | 5 ++-- adapters/verizonmedia/usersync_test.go | 1 - adapters/visx/usersync.go | 2 +- adapters/visx/usersync_test.go | 1 - adapters/vrtcal/usersync.go | 2 +- adapters/vrtcal/usersync_test.go | 1 - adapters/yieldlab/usersync.go | 2 +- adapters/yieldlab/usersync_test.go | 1 - adapters/yieldmo/usersync.go | 2 +- adapters/yieldmo/usersync_test.go | 1 - adapters/yieldone/usersync.go | 2 +- adapters/yieldone/usersync_test.go | 1 - adapters/zeroclickfraud/usersync.go | 2 +- config/bidderinfo.go | 19 +++++++++++-- config/bidderinfo_test.go | 26 +++++++++++++++-- config/test/bidder-info/someBidder.yaml | 1 + endpoints/setuid_test.go | 5 ---- router/router.go | 4 +-- static/bidder-info/33across.yaml | 1 + static/bidder-info/acuityads.yaml | 1 + static/bidder-info/adform.yaml | 1 + static/bidder-info/adkernel.yaml | 1 + static/bidder-info/adkernelAdn.yaml | 1 + static/bidder-info/adman.yaml | 1 + static/bidder-info/admixer.yaml | 1 + static/bidder-info/adocean.yaml | 1 + static/bidder-info/adpone.yaml | 1 + static/bidder-info/adtelligent.yaml | 1 + static/bidder-info/adyoulike.yaml | 1 + static/bidder-info/amx.yaml | 1 + static/bidder-info/appnexus.yaml | 1 + static/bidder-info/avocet.yaml | 1 + static/bidder-info/beachfront.yaml | 1 + static/bidder-info/beintoo.yaml | 1 + static/bidder-info/between.yaml | 1 + static/bidder-info/brightroll.yaml | 1 + static/bidder-info/connectad.yaml | 1 + static/bidder-info/consumable.yaml | 1 + static/bidder-info/conversant.yaml | 1 + static/bidder-info/deepintent.yaml | 1 + static/bidder-info/dmx.yaml | 1 + static/bidder-info/emx_digital.yaml | 1 + static/bidder-info/engagebdr.yaml | 1 + static/bidder-info/eplanning.yaml | 1 + static/bidder-info/gamoshi.yaml | 1 + static/bidder-info/grid.yaml | 1 + static/bidder-info/gumgum.yaml | 1 + static/bidder-info/improvedigital.yaml | 1 + static/bidder-info/invibes.yaml | 1 + static/bidder-info/ix.yaml | 1 + static/bidder-info/lifestreet.yaml | 1 + static/bidder-info/mediafuse.yaml | 1 + static/bidder-info/mgid.yaml | 1 + static/bidder-info/nanointeractive.yaml | 1 + static/bidder-info/nobid.yaml | 1 + static/bidder-info/onetag.yaml | 1 + static/bidder-info/openx.yaml | 1 + static/bidder-info/pubmatic.yaml | 1 + static/bidder-info/pulsepoint.yaml | 1 + static/bidder-info/rhythmone.yaml | 1 + static/bidder-info/rtbhouse.yaml | 1 + static/bidder-info/rubicon.yaml | 1 + static/bidder-info/sharethrough.yaml | 1 + static/bidder-info/smartadserver.yaml | 1 + static/bidder-info/somoaudience.yaml | 1 + static/bidder-info/sonobi.yaml | 1 + static/bidder-info/sovrn.yaml | 1 + static/bidder-info/tappx.yaml | 1 + static/bidder-info/telaria.yaml | 1 + static/bidder-info/triplelift.yaml | 1 + static/bidder-info/triplelift_native.yaml | 1 + static/bidder-info/trustx.yaml | 1 + static/bidder-info/ucfunnel.yaml | 1 + static/bidder-info/unruly.yaml | 1 + static/bidder-info/verizonmedia.yaml | 1 + static/bidder-info/visx.yaml | 1 + static/bidder-info/yieldlab.yaml | 1 + static/bidder-info/yieldmo.yaml | 1 + usersync/usersync.go | 10 ------- usersync/usersyncers/syncer_test.go | 28 ------------------- 231 files changed, 206 insertions(+), 259 deletions(-) diff --git a/adapters/33across/usersync.go b/adapters/33across/usersync.go index 7bc9ae458ae..df26f3b6325 100644 --- a/adapters/33across/usersync.go +++ b/adapters/33across/usersync.go @@ -8,5 +8,5 @@ import ( ) func New33AcrossSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("33across", 58, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("33across", temp, adapters.SyncTypeIframe) } diff --git a/adapters/33across/usersync_test.go b/adapters/33across/usersync_test.go index a9eb4e57908..e99b5965746 100644 --- a/adapters/33across/usersync_test.go +++ b/adapters/33across/usersync_test.go @@ -30,6 +30,5 @@ func Test33AcrossSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr=A&gdpr_consent=B&us_privacy=C&ru=%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 58, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/acuityads/usersync.go b/adapters/acuityads/usersync.go index 7eedf78d229..e2fc1f41961 100644 --- a/adapters/acuityads/usersync.go +++ b/adapters/acuityads/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAcuityAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("acuityads", 231, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("acuityads", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/acuityads/usersync_test.go b/adapters/acuityads/usersync_test.go index 1f57cea3b66..b3ad10bdbb8 100644 --- a/adapters/acuityads/usersync_test.go +++ b/adapters/acuityads/usersync_test.go @@ -29,6 +29,5 @@ func TestAcuityAdsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cs.admanmedia.com/sync/prebid?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 231, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adform/usersync.go b/adapters/adform/usersync.go index 32b4e3a91c1..6a237f794a6 100644 --- a/adapters/adform/usersync.go +++ b/adapters/adform/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdformSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adform", 50, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adform", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adform/usersync_test.go b/adapters/adform/usersync_test.go index 855506da2ed..f133be86583 100644 --- a/adapters/adform/usersync_test.go +++ b/adapters/adform/usersync_test.go @@ -27,6 +27,5 @@ func TestAdformSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 50, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adkernel/usersync.go b/adapters/adkernel/usersync.go index 2e2ebdb9fe3..2de82a00f6e 100644 --- a/adapters/adkernel/usersync.go +++ b/adapters/adkernel/usersync.go @@ -7,8 +7,6 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const adkernelGDPRVendorID = uint16(14) - func NewAdkernelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernel", 14, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adkernel", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adkernel/usersync_test.go b/adapters/adkernel/usersync_test.go index aeacf00b7f0..2ff81668d41 100644 --- a/adapters/adkernel/usersync_test.go +++ b/adapters/adkernel/usersync_test.go @@ -30,6 +30,5 @@ func TestAdkernelAdnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.adkernel.com/user-sync?t=image&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adkernelGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adkernelAdn/usersync.go b/adapters/adkernelAdn/usersync.go index 0bedac38e46..5a890e1565b 100644 --- a/adapters/adkernelAdn/usersync.go +++ b/adapters/adkernelAdn/usersync.go @@ -7,8 +7,6 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const adkernelGDPRVendorID = uint16(14) - func NewAdkernelAdnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernelAdn", 14, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adkernelAdn", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adkernelAdn/usersync_test.go b/adapters/adkernelAdn/usersync_test.go index 92d688e6117..17cf97ce779 100644 --- a/adapters/adkernelAdn/usersync_test.go +++ b/adapters/adkernelAdn/usersync_test.go @@ -30,6 +30,5 @@ func TestAdkernelAdnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://tag.adkernel.com/syncr?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adkernelGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adman/usersync.go b/adapters/adman/usersync.go index aae6afcdfcd..2cb62fe7824 100644 --- a/adapters/adman/usersync.go +++ b/adapters/adman/usersync.go @@ -9,5 +9,5 @@ import ( // NewAdmanSyncer returns adman syncer func NewAdmanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adman", 149, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adman", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adman/usersync_test.go b/adapters/adman/usersync_test.go index 25da77db7ed..d0dc90c8c5d 100644 --- a/adapters/adman/usersync_test.go +++ b/adapters/adman/usersync_test.go @@ -30,6 +30,5 @@ func TestAdmanSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.admanmedia.com/pbs.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 149, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/admixer/usersync.go b/adapters/admixer/usersync.go index 0a7f50ab79a..89e162dff32 100644 --- a/adapters/admixer/usersync.go +++ b/adapters/admixer/usersync.go @@ -1,11 +1,12 @@ package admixer import ( + "text/template" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/usersync" - "text/template" ) func NewAdmixerSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("admixer", 511, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("admixer", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/admixer/usersync_test.go b/adapters/admixer/usersync_test.go index 79f023a236c..6acc48453fb 100644 --- a/adapters/admixer/usersync_test.go +++ b/adapters/admixer/usersync_test.go @@ -30,6 +30,6 @@ func TestAdmixerSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://anyHost/anyPath", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 511, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adocean/usersync.go b/adapters/adocean/usersync.go index 650e517a578..b189f822b46 100644 --- a/adapters/adocean/usersync.go +++ b/adapters/adocean/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdOceanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adocean", 328, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adocean", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adocean/usersync_test.go b/adapters/adocean/usersync_test.go index 9ca81b98cb4..5257017adfa 100644 --- a/adapters/adocean/usersync_test.go +++ b/adapters/adocean/usersync_test.go @@ -30,5 +30,4 @@ func TestAdOceanSyncer(t *testing.T) { syncInfo.URL, ) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 328, syncer.GDPRVendorID()) } diff --git a/adapters/adpone/usersync.go b/adapters/adpone/usersync.go index 67d4c998275..38dec19bb72 100644 --- a/adapters/adpone/usersync.go +++ b/adapters/adpone/usersync.go @@ -7,13 +7,11 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const adponeGDPRVendorID = uint16(799) const adponeFamilyName = "adpone" func NewadponeSyncer(urlTemplate *template.Template) usersync.Usersyncer { return adapters.NewSyncer( adponeFamilyName, - adponeGDPRVendorID, urlTemplate, adapters.SyncTypeRedirect, ) diff --git a/adapters/adpone/usersync_test.go b/adapters/adpone/usersync_test.go index 87b4e9ae440..e744de5eb91 100644 --- a/adapters/adpone/usersync_test.go +++ b/adapters/adpone/usersync_test.go @@ -20,5 +20,4 @@ func TestAdponeSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://usersync.adpone.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adponeGDPRVendorID, syncer.GDPRVendorID()) } diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go index 20bced25c72..d720f110d89 100644 --- a/adapters/adtarget/usersync.go +++ b/adapters/adtarget/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtarget", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtarget", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go index ddba9e7a720..ea6146ceec8 100644 --- a/adapters/adtarget/usersync_test.go +++ b/adapters/adtarget/usersync_test.go @@ -33,6 +33,5 @@ func TestAdtargetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adtelligent/usersync.go b/adapters/adtelligent/usersync.go index 087b5bdd22d..613c8e294f0 100644 --- a/adapters/adtelligent/usersync.go +++ b/adapters/adtelligent/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdtelligentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtelligent", 410, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtelligent", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adtelligent/usersync_test.go b/adapters/adtelligent/usersync_test.go index fa157d226c5..2430f377bd4 100644 --- a/adapters/adtelligent/usersync_test.go +++ b/adapters/adtelligent/usersync_test.go @@ -25,6 +25,5 @@ func TestAdtelligentSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 410, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/advangelists/usersync.go b/adapters/advangelists/usersync.go index 10c46d18a22..83930774773 100644 --- a/adapters/advangelists/usersync.go +++ b/adapters/advangelists/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdvangelistsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("advangelists", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("advangelists", temp, adapters.SyncTypeIframe) } diff --git a/adapters/advangelists/usersync_test.go b/adapters/advangelists/usersync_test.go index 2167d49fb05..04ee7968d87 100644 --- a/adapters/advangelists/usersync_test.go +++ b/adapters/advangelists/usersync_test.go @@ -26,6 +26,5 @@ func TestAdvangelistsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=advangelists&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adyoulike/usersync.go b/adapters/adyoulike/usersync.go index 0ecc4e405dc..ffea6f69a27 100644 --- a/adapters/adyoulike/usersync.go +++ b/adapters/adyoulike/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdyoulikeSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adyoulike", 259, urlTemplate, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adyoulike", urlTemplate, adapters.SyncTypeRedirect) } diff --git a/adapters/adyoulike/usersync_test.go b/adapters/adyoulike/usersync_test.go index 5090acb1a14..72def4cf9b0 100644 --- a/adapters/adyoulike/usersync_test.go +++ b/adapters/adyoulike/usersync_test.go @@ -31,6 +31,5 @@ func TestAdyoulikeSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&gdpr=1&us_privacy=1-YY", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 259, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/aja/usersync.go b/adapters/aja/usersync.go index c54405dbbd1..6a9fad74e32 100644 --- a/adapters/aja/usersync.go +++ b/adapters/aja/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAJASyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("aja", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("aja", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/aja/usersync_test.go b/adapters/aja/usersync_test.go index 4b6c90ef141..dabd5e190b9 100644 --- a/adapters/aja/usersync_test.go +++ b/adapters/aja/usersync_test.go @@ -31,6 +31,5 @@ func TestAJASyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr=1&us_privacy=C&redir=localhost/setuid?bidder=aja&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=%s", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/amx/usersync.go b/adapters/amx/usersync.go index 28e6ac0ed79..17ad04d5cfb 100644 --- a/adapters/amx/usersync.go +++ b/adapters/amx/usersync.go @@ -9,5 +9,5 @@ import ( // NewAMXSyncer produces an AMX RTB usersyncer func NewAMXSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("amx", 737, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("amx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/amx/usersync_test.go b/adapters/amx/usersync_test.go index 20a47c33b69..b6b6e6babe8 100644 --- a/adapters/amx/usersync_test.go +++ b/adapters/amx/usersync_test.go @@ -18,6 +18,5 @@ func TestAMXSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://pbs.amxrtb.com/cchain/0?gdpr=&gdpr_consent=&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 737, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/appnexus/usersync.go b/adapters/appnexus/usersync.go index 22f46f1e723..d29f0e3cb6b 100644 --- a/adapters/appnexus/usersync.go +++ b/adapters/appnexus/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAppnexusSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adnxs", 32, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adnxs", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/appnexus/usersync_test.go b/adapters/appnexus/usersync_test.go index 24b9eede9d6..d01e5704e28 100644 --- a/adapters/appnexus/usersync_test.go +++ b/adapters/appnexus/usersync_test.go @@ -20,6 +20,5 @@ func TestAppNexusSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ib.adnxs.com/getuid?https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 32, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/audienceNetwork/usersync.go b/adapters/audienceNetwork/usersync.go index 45c1281d0ae..4dd0b68ccff 100644 --- a/adapters/audienceNetwork/usersync.go +++ b/adapters/audienceNetwork/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewFacebookSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("audienceNetwork", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("audienceNetwork", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/audienceNetwork/usersync_test.go b/adapters/audienceNetwork/usersync_test.go index c3836e4f154..591ea74f7ba 100644 --- a/adapters/audienceNetwork/usersync_test.go +++ b/adapters/audienceNetwork/usersync_test.go @@ -20,6 +20,5 @@ func TestFacebookSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://www.facebook.com/audiencenetwork/idsync/?partner=partnerId&callback=localhost%2Fsetuid%3Fbidder%3DaudienceNetwork%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/avocet/usersync.go b/adapters/avocet/usersync.go index f1075ab3c52..0cfa055ae86 100644 --- a/adapters/avocet/usersync.go +++ b/adapters/avocet/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAvocetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("avocet", 63, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("avocet", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go index 3df39b77fce..bd4cd4145a2 100644 --- a/adapters/avocet/usersync_test.go +++ b/adapters/avocet/usersync_test.go @@ -30,6 +30,5 @@ func TestAvocetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ads.avct.cloud/getuid?&gdpr=1&gdpr_consent=ConsentString&us_privacy=PrivacyString&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D1%26gdpr_consent%3DConsentString%26uid%3D%7B%7BUUID%7D%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 63, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/beachfront/usersync.go b/adapters/beachfront/usersync.go index a6502331bee..72466b59478 100644 --- a/adapters/beachfront/usersync.go +++ b/adapters/beachfront/usersync.go @@ -7,12 +7,9 @@ import ( "github.com/prebid/prebid-server/usersync" ) -var VENDOR_ID uint16 = 335 - func NewBeachfrontSyncer(temp *template.Template) usersync.Usersyncer { return adapters.NewSyncer( "beachfront", - VENDOR_ID, temp, adapters.SyncTypeIframe) } diff --git a/adapters/beachfront/usersync_test.go b/adapters/beachfront/usersync_test.go index db4d825eb5a..edb52bd4f53 100644 --- a/adapters/beachfront/usersync_test.go +++ b/adapters/beachfront/usersync_test.go @@ -30,6 +30,5 @@ func TestBeachfrontSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.bfmio.com/sync_s2s?gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, uint16(335), syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/beintoo/usersync.go b/adapters/beintoo/usersync.go index a225a2b15da..fb60c6ab0a7 100644 --- a/adapters/beintoo/usersync.go +++ b/adapters/beintoo/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewBeintooSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("Beintoo", 618, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("Beintoo", temp, adapters.SyncTypeIframe) } diff --git a/adapters/beintoo/usersync_test.go b/adapters/beintoo/usersync_test.go index 880d6a84cee..65d92e6d58f 100644 --- a/adapters/beintoo/usersync_test.go +++ b/adapters/beintoo/usersync_test.go @@ -30,6 +30,5 @@ func TestBeintooSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ib.beintoo.com/um?ssp=pbs&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redirect=localhost%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 618, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/between/usersync.go b/adapters/between/usersync.go index a34521fe438..142282730a3 100644 --- a/adapters/between/usersync.go +++ b/adapters/between/usersync.go @@ -9,5 +9,5 @@ import ( // NewBetweenSyncer returns "between" syncer func NewBetweenSyncer(template *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("between", 724, template, adapters.SyncTypeRedirect) + return adapters.NewSyncer("between", template, adapters.SyncTypeRedirect) } diff --git a/adapters/between/usersync_test.go b/adapters/between/usersync_test.go index 6470a9c441c..c54f473b209 100644 --- a/adapters/between/usersync_test.go +++ b/adapters/between/usersync_test.go @@ -1,10 +1,11 @@ package between import ( - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" "testing" "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" ) func TestNewBetweenSyncerSyncer(t *testing.T) { @@ -19,6 +20,5 @@ func TestNewBetweenSyncerSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr=&gdpr_consent=&us_privacy=&callback_url=localhost:8080%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%7BUSER_ID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 724, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/brightroll/usersync.go b/adapters/brightroll/usersync.go index b33cc5a2943..099303fdd01 100644 --- a/adapters/brightroll/usersync.go +++ b/adapters/brightroll/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewBrightrollSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("brightroll", 25, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("brightroll", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/brightroll/usersync_test.go b/adapters/brightroll/usersync_test.go index a5d47e35e56..fac17b0a9ae 100644 --- a/adapters/brightroll/usersync_test.go +++ b/adapters/brightroll/usersync_test.go @@ -20,6 +20,5 @@ func TestBrightrollSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr=&euconsent=&us_privacy=&url=localhost%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 25, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/colossus/usersync.go b/adapters/colossus/usersync.go index a4e82ee3bde..bb25b187352 100644 --- a/adapters/colossus/usersync.go +++ b/adapters/colossus/usersync.go @@ -9,5 +9,5 @@ import ( // NewColossusSyncer returns colossus syncer func NewColossusSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("colossus", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("colossus", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/colossus/usersync_test.go b/adapters/colossus/usersync_test.go index 52eb6389b1c..4cb3a4bbbdf 100644 --- a/adapters/colossus/usersync_test.go +++ b/adapters/colossus/usersync_test.go @@ -30,6 +30,5 @@ func TestColossusSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.colossusssp.com/pbs.gif?gdpr=0&gdpr_consent=A&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/connectad/usersync.go b/adapters/connectad/usersync.go index 5661cb5d9d8..bae96656bce 100644 --- a/adapters/connectad/usersync.go +++ b/adapters/connectad/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewConnectAdSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("connectad", 138, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("connectad", temp, adapters.SyncTypeIframe) } diff --git a/adapters/connectad/usersync_test.go b/adapters/connectad/usersync_test.go index c4b4962b9f6..81b0dc47e33 100644 --- a/adapters/connectad/usersync_test.go +++ b/adapters/connectad/usersync_test.go @@ -30,6 +30,5 @@ func TestConnectAdSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cdn.connectad.io/connectmyusers.php?gdpr=1&consent=fakeconsent&us_privacy=fake&cb=localhost%2Fsetuid%3Fbidder%3Dconnectad%26gdpr%3D1%26gdpr_consent%3Dfakeconsent%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 138, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/consumable/usersync.go b/adapters/consumable/usersync.go index 0e0938c7b39..0cb25ca9864 100644 --- a/adapters/consumable/usersync.go +++ b/adapters/consumable/usersync.go @@ -7,13 +7,9 @@ import ( "github.com/prebid/prebid-server/usersync" ) -var VENDOR_ID uint16 = 591 - func NewConsumableSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( "consumable", - VENDOR_ID, temp, adapters.SyncTypeRedirect) } diff --git a/adapters/consumable/usersync_test.go b/adapters/consumable/usersync_test.go index ef71c0b18c7..0af868a0a62 100644 --- a/adapters/consumable/usersync_test.go +++ b/adapters/consumable/usersync_test.go @@ -30,6 +30,5 @@ func TestConsumableSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//e.serverbid.com/udb/9969/match?gdpr=A&euconsent=B&us_privacy=C&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", u.URL) assert.Equal(t, "redirect", u.Type) - assert.Equal(t, uint16(591), syncer.GDPRVendorID()) assert.Equal(t, false, u.SupportCORS) } diff --git a/adapters/conversant/usersync.go b/adapters/conversant/usersync.go index c2676df6620..a38631f282c 100644 --- a/adapters/conversant/usersync.go +++ b/adapters/conversant/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewConversantSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("conversant", 24, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("conversant", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/conversant/usersync_test.go b/adapters/conversant/usersync_test.go index 16affbd1d32..62ab732c443 100644 --- a/adapters/conversant/usersync_test.go +++ b/adapters/conversant/usersync_test.go @@ -25,6 +25,5 @@ func TestConversantSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "usersync?rurl=localhost%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D0%26gdpr_consent%3D%26uid%3D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 24, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/cpmstar/usersync.go b/adapters/cpmstar/usersync.go index 9c864e24ce3..d3086f65b24 100644 --- a/adapters/cpmstar/usersync.go +++ b/adapters/cpmstar/usersync.go @@ -9,5 +9,5 @@ import ( //NewCpmstarSyncer : func NewCpmstarSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("cpmstar", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("cpmstar", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/cpmstar/usersync_test.go b/adapters/cpmstar/usersync_test.go index dae55e6302e..9bae7062b1c 100644 --- a/adapters/cpmstar/usersync_test.go +++ b/adapters/cpmstar/usersync_test.go @@ -20,6 +20,5 @@ func TestCpmstarSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://server.cpmstar.com/usersync.aspx?gdpr=&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D%26gdpr_consent%3D%26us_privacy%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/datablocks/usersync.go b/adapters/datablocks/usersync.go index 2b47b259e39..2c69963ae74 100644 --- a/adapters/datablocks/usersync.go +++ b/adapters/datablocks/usersync.go @@ -7,8 +7,6 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const datablocksGDPRVendorID = uint16(0) - func NewDatablocksSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("datablocks", datablocksGDPRVendorID, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("datablocks", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/datablocks/usersync_test.go b/adapters/datablocks/usersync_test.go index a7518e9b226..d6ee580408a 100644 --- a/adapters/datablocks/usersync_test.go +++ b/adapters/datablocks/usersync_test.go @@ -30,6 +30,5 @@ func TestDatablocksSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, datablocksGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/deepintent/usersync.go b/adapters/deepintent/usersync.go index 9e803df6d9d..7a17f5336d5 100644 --- a/adapters/deepintent/usersync.go +++ b/adapters/deepintent/usersync.go @@ -9,5 +9,5 @@ import ( // NewDeepintentSyncer returns deepintent syncer func NewDeepintentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("deepintent", 541, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("deepintent", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/deepintent/usersync_test.go b/adapters/deepintent/usersync_test.go index 06a221ca2fd..6709a838100 100644 --- a/adapters/deepintent/usersync_test.go +++ b/adapters/deepintent/usersync_test.go @@ -30,6 +30,5 @@ func TestDeepintentSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://match.deepintent.com/usersync/136?id=unk&gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, uint16(541), syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/dmx/usersync.go b/adapters/dmx/usersync.go index 98e56234fa6..07fedf1c124 100644 --- a/adapters/dmx/usersync.go +++ b/adapters/dmx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewDmxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("dmx", 144, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("dmx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/dmx/usersync_test.go b/adapters/dmx/usersync_test.go index e4e3c7d8e55..a316af04cfb 100644 --- a/adapters/dmx/usersync_test.go +++ b/adapters/dmx/usersync_test.go @@ -1,10 +1,11 @@ package dmx import ( - "github.com/prebid/prebid-server/privacy" "testing" "text/template" + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" ) @@ -15,6 +16,5 @@ func TestDmxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://dmx.districtm.io/s/v1/img/s/10007", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 144, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/emx_digital/usersync.go b/adapters/emx_digital/usersync.go index a453955b22e..2d3011e0d69 100644 --- a/adapters/emx_digital/usersync.go +++ b/adapters/emx_digital/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewEMXDigitalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("emx_digital", 183, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("emx_digital", temp, adapters.SyncTypeIframe) } diff --git a/adapters/emx_digital/usersync_test.go b/adapters/emx_digital/usersync_test.go index 59d66d87808..7aaf133512f 100644 --- a/adapters/emx_digital/usersync_test.go +++ b/adapters/emx_digital/usersync_test.go @@ -30,6 +30,5 @@ func TestEMXDigitalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cs.emxdgt.com/um?ssp=pbs&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redirect=localhost%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 183, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/engagebdr/usersync.go b/adapters/engagebdr/usersync.go index ae4047aa89c..205097b07be 100644 --- a/adapters/engagebdr/usersync.go +++ b/adapters/engagebdr/usersync.go @@ -1,11 +1,12 @@ package engagebdr import ( + "text/template" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/usersync" - "text/template" ) func NewEngageBDRSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("engagebdr", 62, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("engagebdr", temp, adapters.SyncTypeIframe) } diff --git a/adapters/engagebdr/usersync_test.go b/adapters/engagebdr/usersync_test.go index 3a6c179addf..5cee3abe35c 100644 --- a/adapters/engagebdr/usersync_test.go +++ b/adapters/engagebdr/usersync_test.go @@ -30,6 +30,5 @@ func TestEngageBDRSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://match.bnmla.com/usersync/s2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&r=localhost%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 62, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/eplanning/usersync.go b/adapters/eplanning/usersync.go index faa7fa82a19..6e8abf5de27 100644 --- a/adapters/eplanning/usersync.go +++ b/adapters/eplanning/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewEPlanningSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("eplanning", 90, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("eplanning", temp, adapters.SyncTypeIframe) } diff --git a/adapters/eplanning/usersync_test.go b/adapters/eplanning/usersync_test.go index 85770689024..922ab4e20ce 100644 --- a/adapters/eplanning/usersync_test.go +++ b/adapters/eplanning/usersync_test.go @@ -20,6 +20,5 @@ func TestEPlanningSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3Flocalhost%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 90, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/gamma/usersync.go b/adapters/gamma/usersync.go index f19c522cebc..4e4412a1efd 100644 --- a/adapters/gamma/usersync.go +++ b/adapters/gamma/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGammaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gamma", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("gamma", temp, adapters.SyncTypeIframe) } diff --git a/adapters/gamma/usersync_test.go b/adapters/gamma/usersync_test.go index 4278f9abd36..7636e05a6ef 100644 --- a/adapters/gamma/usersync_test.go +++ b/adapters/gamma/usersync_test.go @@ -25,6 +25,5 @@ func TestGammaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//hb.gammaplatform.com/sync?gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dgamma%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/gamoshi/usersync.go b/adapters/gamoshi/usersync.go index 6b7c43dd6a2..9d6cdd0e518 100644 --- a/adapters/gamoshi/usersync.go +++ b/adapters/gamoshi/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGamoshiSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gamoshi", 644, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("gamoshi", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/gamoshi/usersync_test.go b/adapters/gamoshi/usersync_test.go index 43dc88a4953..0465365f5e3 100644 --- a/adapters/gamoshi/usersync_test.go +++ b/adapters/gamoshi/usersync_test.go @@ -25,6 +25,5 @@ func TestGamoshiSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.gamoshi.io/user_sync_prebid?gdpr=&consent=&us_privacy=anyValue&rurl=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D%7B%7B.GDPR%7D%7D%26gdpr_consent%3D%7B%7B.GDPRConsent%7D%7D%26uid%3D%5Bgusr%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 644, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/grid/usersync.go b/adapters/grid/usersync.go index afdc5db763c..31788ea3f06 100644 --- a/adapters/grid/usersync.go +++ b/adapters/grid/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGridSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("grid", 686, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("grid", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/grid/usersync_test.go b/adapters/grid/usersync_test.go index 99730b5deb4..2d7f3fc6a4f 100644 --- a/adapters/grid/usersync_test.go +++ b/adapters/grid/usersync_test.go @@ -25,6 +25,5 @@ func TestGridSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 686, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/gumgum/usersync.go b/adapters/gumgum/usersync.go index 5d29c7dceb2..defef87dad9 100644 --- a/adapters/gumgum/usersync.go +++ b/adapters/gumgum/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewGumGumSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gumgum", 61, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("gumgum", temp, adapters.SyncTypeIframe) } diff --git a/adapters/gumgum/usersync_test.go b/adapters/gumgum/usersync_test.go index 9c6dc420600..9b7c7aa4578 100644 --- a/adapters/gumgum/usersync_test.go +++ b/adapters/gumgum/usersync_test.go @@ -30,6 +30,5 @@ func TestGumGumSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.gumgum.com/usync/prbds2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&r=localhost%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 61, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/improvedigital/usersync.go b/adapters/improvedigital/usersync.go index 72c3ddafd49..a58b97c3864 100644 --- a/adapters/improvedigital/usersync.go +++ b/adapters/improvedigital/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewImprovedigitalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("improvedigital", 253, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("improvedigital", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/improvedigital/usersync_test.go b/adapters/improvedigital/usersync_test.go index 35ea89cf894..9892e0cc703 100644 --- a/adapters/improvedigital/usersync_test.go +++ b/adapters/improvedigital/usersync_test.go @@ -30,6 +30,5 @@ func TestImprovedigitalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.360yield.com/server_match?gdpr=A&gdpr_consent=B&us_privacy=C&r=%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%7BPUB_USER_ID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 253, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/invibes/usersync.go b/adapters/invibes/usersync.go index 468e604c1a6..e6922444d44 100644 --- a/adapters/invibes/usersync.go +++ b/adapters/invibes/usersync.go @@ -10,7 +10,6 @@ import ( func NewInvibesSyncer(urlTemplate *template.Template) usersync.Usersyncer { return adapters.NewSyncer( "invibes", - 436, urlTemplate, adapters.SyncTypeIframe, ) diff --git a/adapters/invibes/usersync_test.go b/adapters/invibes/usersync_test.go index 3f715f6bdfe..492886228fd 100644 --- a/adapters/invibes/usersync_test.go +++ b/adapters/invibes/usersync_test.go @@ -26,7 +26,6 @@ func TestInvibesSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://localhost:56479/home/getLid?gdpr=1&gdpr_consent=abc&us_privacy=&redirectUri=test.com%2Fsetuid%3Fbidder%3Dinvibes%26gdpr%3D1%26gdpr_consent%3Dabc%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 436, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/ix/usersync.go b/adapters/ix/usersync.go index 6f3558949e0..621fa17945a 100644 --- a/adapters/ix/usersync.go +++ b/adapters/ix/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewIxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ix", 10, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("ix", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/ix/usersync_test.go b/adapters/ix/usersync_test.go index 10f91da1abe..3288b1ae443 100644 --- a/adapters/ix/usersync_test.go +++ b/adapters/ix/usersync_test.go @@ -20,6 +20,5 @@ func TestIxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ssum-sec.casalemedia.com/usermatchredir?s=184932&cb=localhost%2Fsetuid%3Fbidder%3Dix%26gdpr%3D%26gdpr_consent%3D%26uid%3D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 10, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/jixie/jixie_test.go b/adapters/jixie/jixie_test.go index 968fb4fdc9c..289dd241f23 100644 --- a/adapters/jixie/jixie_test.go +++ b/adapters/jixie/jixie_test.go @@ -1,10 +1,11 @@ package jixie import ( + "testing" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "testing" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/jixie/usersync.go b/adapters/jixie/usersync.go index 8c1d5c07374..137d78f2859 100644 --- a/adapters/jixie/usersync.go +++ b/adapters/jixie/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewJixieSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("jixie", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("jixie", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/jixie/usersync_test.go b/adapters/jixie/usersync_test.go index 5be5e51433e..575482435ff 100644 --- a/adapters/jixie/usersync_test.go +++ b/adapters/jixie/usersync_test.go @@ -1,10 +1,10 @@ package jixie import ( - "github.com/prebid/prebid-server/privacy" "testing" "text/template" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestJixieSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://id.jixie.io/api/sync?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25JXUID%25%25", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/krushmedia/usersync.go b/adapters/krushmedia/usersync.go index 5dc1471fb9f..860cb2204e9 100644 --- a/adapters/krushmedia/usersync.go +++ b/adapters/krushmedia/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewKrushmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("krushmedia", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("krushmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/krushmedia/usersync_test.go b/adapters/krushmedia/usersync_test.go index b58f2f1bc4e..765b0faa18b 100644 --- a/adapters/krushmedia/usersync_test.go +++ b/adapters/krushmedia/usersync_test.go @@ -29,6 +29,5 @@ func TestKrushmediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr=0&gdpr_consent=allGdpr&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/lifestreet/usersync.go b/adapters/lifestreet/usersync.go index 15ffde4db74..f5300ebaa90 100644 --- a/adapters/lifestreet/usersync.go +++ b/adapters/lifestreet/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewLifestreetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lifestreet", 67, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("lifestreet", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/lifestreet/usersync_test.go b/adapters/lifestreet/usersync_test.go index 8fee09358e8..e41217fe10f 100644 --- a/adapters/lifestreet/usersync_test.go +++ b/adapters/lifestreet/usersync_test.go @@ -25,6 +25,5 @@ func TestLifestreetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 67, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/lockerdome/usersync.go b/adapters/lockerdome/usersync.go index fb01f8e2d99..d5cce16804a 100644 --- a/adapters/lockerdome/usersync.go +++ b/adapters/lockerdome/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewLockerDomeSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lockerdome", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("lockerdome", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/lockerdome/usersync_test.go b/adapters/lockerdome/usersync_test.go index b5dea2261b6..3a2bd7f325b 100644 --- a/adapters/lockerdome/usersync_test.go +++ b/adapters/lockerdome/usersync_test.go @@ -20,6 +20,5 @@ func TestLockerDomeSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://lockerdome.com/usync/prebidserver?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7B%7Buid%7D%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/logicad/usersync.go b/adapters/logicad/usersync.go index d26a197b0a1..e685cc985fc 100644 --- a/adapters/logicad/usersync.go +++ b/adapters/logicad/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewLogicadSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("logicad", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("logicad", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/logicad/usersync_test.go b/adapters/logicad/usersync_test.go index 89d6207d348..e8b10c665fe 100644 --- a/adapters/logicad/usersync_test.go +++ b/adapters/logicad/usersync_test.go @@ -26,6 +26,5 @@ func TestLogicadSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://localhost/cookiesender?r=true&gdpr=1&gdpr_consent=A&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/lunamedia/usersync.go b/adapters/lunamedia/usersync.go index 7ad54e384a1..39c9a808040 100644 --- a/adapters/lunamedia/usersync.go +++ b/adapters/lunamedia/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewLunaMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lunamedia", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("lunamedia", temp, adapters.SyncTypeIframe) } diff --git a/adapters/lunamedia/usersync_test.go b/adapters/lunamedia/usersync_test.go index c9fe2032d2c..24cd740d600 100644 --- a/adapters/lunamedia/usersync_test.go +++ b/adapters/lunamedia/usersync_test.go @@ -26,6 +26,5 @@ func TestLunaMediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/marsmedia/usersync.go b/adapters/marsmedia/usersync.go index 50996f325ac..4ac76d1f5f2 100644 --- a/adapters/marsmedia/usersync.go +++ b/adapters/marsmedia/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewMarsmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("marsmedia", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("marsmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/marsmedia/usersync_test.go b/adapters/marsmedia/usersync_test.go index f019c014516..975af65fcf5 100644 --- a/adapters/marsmedia/usersync_test.go +++ b/adapters/marsmedia/usersync_test.go @@ -30,7 +30,6 @@ func TestMarsmediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr=A&gdpr_consent=B&us_privacy=C&redirect=localhost:8000%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mediafuse/usersync.go b/adapters/mediafuse/usersync.go index 5381bf3606d..b91a6b5052c 100644 --- a/adapters/mediafuse/usersync.go +++ b/adapters/mediafuse/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewMediafuseSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mediafuse", 411, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("mediafuse", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/mediafuse/usersync_test.go b/adapters/mediafuse/usersync_test.go index 30b5b535b12..e3dfa06831d 100644 --- a/adapters/mediafuse/usersync_test.go +++ b/adapters/mediafuse/usersync_test.go @@ -25,6 +25,5 @@ func TestMediafuseSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.hbmp.mediafuse.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 411, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mgid/usersync.go b/adapters/mgid/usersync.go index fbdb95f01fc..94cf12e119d 100644 --- a/adapters/mgid/usersync.go +++ b/adapters/mgid/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewMgidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mgid", 358, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("mgid", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/mgid/usersync_test.go b/adapters/mgid/usersync_test.go index 2ce634eeac2..3fa5a151bd8 100644 --- a/adapters/mgid/usersync_test.go +++ b/adapters/mgid/usersync_test.go @@ -25,6 +25,5 @@ func TestMgidSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cm.mgid.com/m?cdsp=363893&adu=https%3A//external.com%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Bmuidn%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 358, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/nanointeractive/usersync.go b/adapters/nanointeractive/usersync.go index e6227436fb2..6bd9cd1f036 100644 --- a/adapters/nanointeractive/usersync.go +++ b/adapters/nanointeractive/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewNanoInteractiveSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nanointeractive", 72, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("nanointeractive", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/nanointeractive/usersync_test.go b/adapters/nanointeractive/usersync_test.go index fa78664928f..4d816ab7384 100644 --- a/adapters/nanointeractive/usersync_test.go +++ b/adapters/nanointeractive/usersync_test.go @@ -31,6 +31,5 @@ func TestNewNanoInteractiveSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr=1&consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 72, userSync.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/ninthdecimal/usersync.go b/adapters/ninthdecimal/usersync.go index 7a8d029cfa9..a01fdb636e3 100755 --- a/adapters/ninthdecimal/usersync.go +++ b/adapters/ninthdecimal/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewNinthDecimalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ninthdecimal", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("ninthdecimal", temp, adapters.SyncTypeIframe) } diff --git a/adapters/ninthdecimal/usersync_test.go b/adapters/ninthdecimal/usersync_test.go index ee121434f29..e722a2b6e69 100755 --- a/adapters/ninthdecimal/usersync_test.go +++ b/adapters/ninthdecimal/usersync_test.go @@ -26,6 +26,5 @@ func TestNinthDecimalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/nobid/usersync.go b/adapters/nobid/usersync.go index c21413f59e7..442075648ce 100644 --- a/adapters/nobid/usersync.go +++ b/adapters/nobid/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewNoBidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nobid", 816, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("nobid", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/onetag/usersync.go b/adapters/onetag/usersync.go index f36b82a853c..9a2b700dd3d 100644 --- a/adapters/onetag/usersync.go +++ b/adapters/onetag/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSyncer(template *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("onetag", 241, template, adapters.SyncTypeIframe) + return adapters.NewSyncer("onetag", template, adapters.SyncTypeIframe) } diff --git a/adapters/onetag/usersync_test.go b/adapters/onetag/usersync_test.go index 31892e6d9c8..21f4837d5e1 100644 --- a/adapters/onetag/usersync_test.go +++ b/adapters/onetag/usersync_test.go @@ -20,5 +20,4 @@ func TestOneTagSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://onetag-sys.com/usync/?redir=", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 241, syncer.GDPRVendorID()) } diff --git a/adapters/openx/usersync.go b/adapters/openx/usersync.go index bb4b328ce62..875b60fbd10 100644 --- a/adapters/openx/usersync.go +++ b/adapters/openx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewOpenxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("openx", 69, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("openx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/openx/usersync_test.go b/adapters/openx/usersync_test.go index cdbb7da8b95..14ec38be118 100644 --- a/adapters/openx/usersync_test.go +++ b/adapters/openx/usersync_test.go @@ -20,6 +20,5 @@ func TestOpenxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.openx.net/sync/prebid?gdpr=&gdpr_consent=&r=localhost%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 69, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/pubmatic/usersync.go b/adapters/pubmatic/usersync.go index 7b4d8e86b50..822f13cea3d 100644 --- a/adapters/pubmatic/usersync.go +++ b/adapters/pubmatic/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewPubmaticSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pubmatic", 76, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("pubmatic", temp, adapters.SyncTypeIframe) } diff --git a/adapters/pubmatic/usersync_test.go b/adapters/pubmatic/usersync_test.go index d6cd9f78af7..0f4d2e857d3 100644 --- a/adapters/pubmatic/usersync_test.go +++ b/adapters/pubmatic/usersync_test.go @@ -30,6 +30,5 @@ func TestPubmaticSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr=A&gdpr_consent=B&us_privacy=C&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 76, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/pulsepoint/usersync.go b/adapters/pulsepoint/usersync.go index 58de835be28..1b6903f9f02 100644 --- a/adapters/pulsepoint/usersync.go +++ b/adapters/pulsepoint/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewPulsepointSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pulsepoint", 81, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("pulsepoint", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/pulsepoint/usersync_test.go b/adapters/pulsepoint/usersync_test.go index e1680fd22ba..7cfea57cb91 100644 --- a/adapters/pulsepoint/usersync_test.go +++ b/adapters/pulsepoint/usersync_test.go @@ -20,6 +20,5 @@ func TestPulsepointSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//bh.contextweb.com/rtset?pid=561205&ev=1&rurl=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25VGUID%25%25", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 81, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/rhythmone/usersync.go b/adapters/rhythmone/usersync.go index 0f7db388e5c..990fcb065f6 100644 --- a/adapters/rhythmone/usersync.go +++ b/adapters/rhythmone/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewRhythmoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rhythmone", 36, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("rhythmone", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/rhythmone/usersync_test.go b/adapters/rhythmone/usersync_test.go index 85ecba2a8ab..97920fb4980 100644 --- a/adapters/rhythmone/usersync_test.go +++ b/adapters/rhythmone/usersync_test.go @@ -30,6 +30,5 @@ func TestRhythmoneSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.1rx.io/usersync2/rmphb?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redir=localhost%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%5BRX_UUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 36, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/rtbhouse/usersync.go b/adapters/rtbhouse/usersync.go index 3e38d67e593..baa0b994373 100644 --- a/adapters/rtbhouse/usersync.go +++ b/adapters/rtbhouse/usersync.go @@ -7,13 +7,11 @@ import ( "github.com/prebid/prebid-server/usersync" ) -const rtbHouseGDPRVendorID = uint16(16) const rtbHouseFamilyName = "rtbhouse" func NewRTBHouseSyncer(urlTemplate *template.Template) usersync.Usersyncer { return adapters.NewSyncer( rtbHouseFamilyName, - rtbHouseGDPRVendorID, urlTemplate, adapters.SyncTypeRedirect, ) diff --git a/adapters/rtbhouse/usersync_test.go b/adapters/rtbhouse/usersync_test.go index bb139f7a2e5..a7cb2590f90 100644 --- a/adapters/rtbhouse/usersync_test.go +++ b/adapters/rtbhouse/usersync_test.go @@ -25,6 +25,5 @@ func TestRTBHouseSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://creativecdn.com/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, rtbHouseGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/rubicon/usersync.go b/adapters/rubicon/usersync.go index 08d98825a1e..a4ab464a73f 100644 --- a/adapters/rubicon/usersync.go +++ b/adapters/rubicon/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewRubiconSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rubicon", 52, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("rubicon", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/rubicon/usersync_test.go b/adapters/rubicon/usersync_test.go index 646ebff2dc4..eca4056206e 100644 --- a/adapters/rubicon/usersync_test.go +++ b/adapters/rubicon/usersync_test.go @@ -25,7 +25,6 @@ func TestRubiconSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://pixel.rubiconproject.com/exchange/sync.php?p=prebid&gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 52, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "rubicon", syncer.FamilyName()) } diff --git a/adapters/sharethrough/usersync.go b/adapters/sharethrough/usersync.go index a951fcd6a0a..f76f41ca83e 100644 --- a/adapters/sharethrough/usersync.go +++ b/adapters/sharethrough/usersync.go @@ -1,11 +1,12 @@ package sharethrough import ( + "text/template" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/usersync" - "text/template" ) func NewSharethroughSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sharethrough", 80, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sharethrough", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sharethrough/usersync_test.go b/adapters/sharethrough/usersync_test.go index 4802329554e..00b3c427fb8 100644 --- a/adapters/sharethrough/usersync_test.go +++ b/adapters/sharethrough/usersync_test.go @@ -25,7 +25,6 @@ func TestSharethroughSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://match.sharethrough.com?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 80, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "sharethrough", syncer.FamilyName()) } diff --git a/adapters/smartadserver/usersync.go b/adapters/smartadserver/usersync.go index 95b305ff227..f7199965441 100644 --- a/adapters/smartadserver/usersync.go +++ b/adapters/smartadserver/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSmartadserverSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartadserver", 45, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartadserver", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartadserver/usersync_test.go b/adapters/smartadserver/usersync_test.go index c4e6660693f..319ce5b58a6 100644 --- a/adapters/smartadserver/usersync_test.go +++ b/adapters/smartadserver/usersync_test.go @@ -30,6 +30,5 @@ func TestSmartadserverSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ssbsync.smartadserver.com/getuid?gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%5Bsas_uid%5D%22", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 45, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/smartrtb/usersync.go b/adapters/smartrtb/usersync.go index 2f7b1dc3339..74ef0e9960b 100644 --- a/adapters/smartrtb/usersync.go +++ b/adapters/smartrtb/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSmartRTBSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartrtb", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartrtb", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartrtb/usersync_test.go b/adapters/smartrtb/usersync_test.go index ae3ae5dc007..68a8452a316 100644 --- a/adapters/smartrtb/usersync_test.go +++ b/adapters/smartrtb/usersync_test.go @@ -15,6 +15,5 @@ func TestSmartRTBSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr=&gdpr_consent=&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/smartyads/usersync.go b/adapters/smartyads/usersync.go index 839621e9927..9075aa9bcd7 100644 --- a/adapters/smartyads/usersync.go +++ b/adapters/smartyads/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSmartyAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartyads", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartyads", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartyads/usersync_test.go b/adapters/smartyads/usersync_test.go index ed7fc07b594..4f94591c634 100644 --- a/adapters/smartyads/usersync_test.go +++ b/adapters/smartyads/usersync_test.go @@ -29,6 +29,5 @@ func TestSmartyAdsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://as.ck-ie.com/prebid.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/somoaudience/usersync.go b/adapters/somoaudience/usersync.go index e1e7e2ef21d..5d1ddd71bc6 100644 --- a/adapters/somoaudience/usersync.go +++ b/adapters/somoaudience/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSomoaudienceSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("somoaudience", 341, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("somoaudience", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/somoaudience/usersync_test.go b/adapters/somoaudience/usersync_test.go index 86460506d68..2367a5674dd 100644 --- a/adapters/somoaudience/usersync_test.go +++ b/adapters/somoaudience/usersync_test.go @@ -20,6 +20,5 @@ func TestSomoaudienceSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//publisher-east.mobileadtrading.com/usersync?ru=localhost%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 341, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/sonobi/usersync.go b/adapters/sonobi/usersync.go index 6986d0fd8a1..6fedd8bfa05 100644 --- a/adapters/sonobi/usersync.go +++ b/adapters/sonobi/usersync.go @@ -1,11 +1,12 @@ package sonobi import ( + "text/template" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/usersync" - "text/template" ) func NewSonobiSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sonobi", 104, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sonobi", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sonobi/usersync_test.go b/adapters/sonobi/usersync_test.go index bfe5859e5d1..995c3757ba4 100644 --- a/adapters/sonobi/usersync_test.go +++ b/adapters/sonobi/usersync_test.go @@ -25,6 +25,5 @@ func TestSonobiSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.go.sonobi.com/us.gif?loc=external.com%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D0%26gdpr%3D%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 104, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/sovrn/usersync.go b/adapters/sovrn/usersync.go index b4de623718a..225f2888196 100644 --- a/adapters/sovrn/usersync.go +++ b/adapters/sovrn/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSovrnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sovrn", 13, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sovrn", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sovrn/usersync_test.go b/adapters/sovrn/usersync_test.go index 4a4dfd9d1b9..6c35ecdb05d 100644 --- a/adapters/sovrn/usersync_test.go +++ b/adapters/sovrn/usersync_test.go @@ -25,6 +25,5 @@ func TestSovrnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ap.lijit.com/pixel?redir=external.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 13, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/synacormedia/usersync.go b/adapters/synacormedia/usersync.go index 988c1b9ef6a..c7fa5c42aea 100644 --- a/adapters/synacormedia/usersync.go +++ b/adapters/synacormedia/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewSynacorMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("synacormedia", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("synacormedia", temp, adapters.SyncTypeIframe) } diff --git a/adapters/synacormedia/usersync_test.go b/adapters/synacormedia/usersync_test.go index 538efb30c73..ce45353c7e5 100644 --- a/adapters/synacormedia/usersync_test.go +++ b/adapters/synacormedia/usersync_test.go @@ -20,6 +20,5 @@ func TestSynacormediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.technoratimedia.com/services?srv=cs&pid=70&cb=localhost%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/syncer.go b/adapters/syncer.go index 122bcc7ed38..b9752b50ce1 100644 --- a/adapters/syncer.go +++ b/adapters/syncer.go @@ -4,34 +4,21 @@ import ( "text/template" "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/usersync" ) -func GDPRAwareSyncerIDs(syncers map[openrtb_ext.BidderName]usersync.Usersyncer) map[openrtb_ext.BidderName]uint16 { - gdprAwareSyncers := make(map[openrtb_ext.BidderName]uint16, len(syncers)) - for bidderName, syncer := range syncers { - if syncer.GDPRVendorID() != 0 { - gdprAwareSyncers[bidderName] = syncer.GDPRVendorID() - } - } - return gdprAwareSyncers -} - type Syncer struct { - familyName string - gdprVendorID uint16 - urlTemplate *template.Template - syncType SyncType + familyName string + syncType SyncType + urlTemplate *template.Template } -func NewSyncer(familyName string, vendorID uint16, urlTemplate *template.Template, syncType SyncType) *Syncer { +func NewSyncer(familyName string, urlTemplate *template.Template, syncType SyncType) *Syncer { return &Syncer{ - familyName: familyName, - gdprVendorID: vendorID, - urlTemplate: urlTemplate, - syncType: syncType, + familyName: familyName, + urlTemplate: urlTemplate, + syncType: syncType, } } @@ -62,7 +49,3 @@ func (s *Syncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.Us func (s *Syncer) FamilyName() string { return s.familyName } - -func (s *Syncer) GDPRVendorID() uint16 { - return s.gdprVendorID -} diff --git a/adapters/tappx/usersync.go b/adapters/tappx/usersync.go index 7bf6b9a6094..01477c35363 100644 --- a/adapters/tappx/usersync.go +++ b/adapters/tappx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewTappxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("tappx", 628, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("tappx", temp, adapters.SyncTypeIframe) } diff --git a/adapters/tappx/usersync_test.go b/adapters/tappx/usersync_test.go index 65919b9f1f1..992a35de9a8 100644 --- a/adapters/tappx/usersync_test.go +++ b/adapters/tappx/usersync_test.go @@ -30,6 +30,5 @@ func TestTappxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin=1&gdpr_consent=A&us_privacy=1YNN&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%7B%7BTPPXUID%7D%7D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 628, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/telaria/usersync.go b/adapters/telaria/usersync.go index e3f76f6e9b4..76be62026f8 100644 --- a/adapters/telaria/usersync.go +++ b/adapters/telaria/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewTelariaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("telaria", 202, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("telaria", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/telaria/usersync_test.go b/adapters/telaria/usersync_test.go index 4896b253d2f..f019ed5ceaf 100644 --- a/adapters/telaria/usersync_test.go +++ b/adapters/telaria/usersync_test.go @@ -26,7 +26,6 @@ func TestTelariaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://pbs.publishers.tremorhub.com/pubsync?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 202, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "telaria", syncer.FamilyName()) diff --git a/adapters/triplelift/usersync.go b/adapters/triplelift/usersync.go index 5cb524bea11..4a47615bd29 100644 --- a/adapters/triplelift/usersync.go +++ b/adapters/triplelift/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", 28, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/triplelift/usersync_test.go b/adapters/triplelift/usersync_test.go index 1731b22dffb..012e33a4d0a 100644 --- a/adapters/triplelift/usersync_test.go +++ b/adapters/triplelift/usersync_test.go @@ -20,6 +20,5 @@ func TestTripleliftSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 28, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/triplelift_native/usersync.go b/adapters/triplelift_native/usersync.go index b7587b77950..2a07740a761 100644 --- a/adapters/triplelift_native/usersync.go +++ b/adapters/triplelift_native/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", 28, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/triplelift_native/usersync_test.go b/adapters/triplelift_native/usersync_test.go index df0757edc9f..7fd878b7f92 100644 --- a/adapters/triplelift_native/usersync_test.go +++ b/adapters/triplelift_native/usersync_test.go @@ -20,6 +20,5 @@ func TestTripleliftSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 28, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/trustx/usersync.go b/adapters/trustx/usersync.go index a617cd716a4..a738e8e9b79 100644 --- a/adapters/trustx/usersync.go +++ b/adapters/trustx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewTrustXSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("trustx", 686, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("trustx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/trustx/usersync_test.go b/adapters/trustx/usersync_test.go index ced0d21552b..ba371bc2061 100644 --- a/adapters/trustx/usersync_test.go +++ b/adapters/trustx/usersync_test.go @@ -25,6 +25,5 @@ func TestTrustXSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 686, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/ucfunnel/usersync.go b/adapters/ucfunnel/usersync.go index 92eba0d73e0..0ae9948dd77 100644 --- a/adapters/ucfunnel/usersync.go +++ b/adapters/ucfunnel/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewUcfunnelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ucfunnel", 607, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("ucfunnel", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/ucfunnel/usersync_test.go b/adapters/ucfunnel/usersync_test.go index 45320b8cac1..204de978150 100644 --- a/adapters/ucfunnel/usersync_test.go +++ b/adapters/ucfunnel/usersync_test.go @@ -25,6 +25,5 @@ func TestUcfunnelSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.aralego.com/idsync?gdpr=0&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 607, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/unruly/usersync.go b/adapters/unruly/usersync.go index c90052a475f..5dc60c859bb 100644 --- a/adapters/unruly/usersync.go +++ b/adapters/unruly/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewUnrulySyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("unruly", 162, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("unruly", temp, adapters.SyncTypeIframe) } diff --git a/adapters/unruly/usersync_test.go b/adapters/unruly/usersync_test.go index 2f0e07d813a..f0702cebd34 100644 --- a/adapters/unruly/usersync_test.go +++ b/adapters/unruly/usersync_test.go @@ -30,6 +30,5 @@ func TestUnrulySyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr=A&consent=B&us_privacy=C&rurl=%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 162, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/valueimpression/usersync.go b/adapters/valueimpression/usersync.go index 34addbc0e75..08490a5ed3e 100644 --- a/adapters/valueimpression/usersync.go +++ b/adapters/valueimpression/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewValueImpressionSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("valueimpression", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("valueimpression", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go index ffb3f372bd7..7b3a13c5dd6 100644 --- a/adapters/valueimpression/usersync_test.go +++ b/adapters/valueimpression/usersync_test.go @@ -30,6 +30,5 @@ func TestValueImpressionSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.valueimpression.com/usersync?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/verizonmedia/usersync.go b/adapters/verizonmedia/usersync.go index 31fb12a2064..2c7354d1146 100644 --- a/adapters/verizonmedia/usersync.go +++ b/adapters/verizonmedia/usersync.go @@ -1,11 +1,12 @@ package verizonmedia import ( + "text/template" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/usersync" - "text/template" ) func NewVerizonMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("verizonmedia", 25, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("verizonmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/verizonmedia/usersync_test.go b/adapters/verizonmedia/usersync_test.go index ad77ef0e6cb..9dd1ae70c25 100644 --- a/adapters/verizonmedia/usersync_test.go +++ b/adapters/verizonmedia/usersync_test.go @@ -19,5 +19,4 @@ func TestVerizonMediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 25, syncer.GDPRVendorID()) } diff --git a/adapters/visx/usersync.go b/adapters/visx/usersync.go index fe1f5a42a10..dc143570ab9 100644 --- a/adapters/visx/usersync.go +++ b/adapters/visx/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewVisxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("visx", 154, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("visx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/visx/usersync_test.go b/adapters/visx/usersync_test.go index b410cda6061..ec4cf82b04b 100644 --- a/adapters/visx/usersync_test.go +++ b/adapters/visx/usersync_test.go @@ -30,6 +30,5 @@ func TestVisxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://t.visx.net/s2s_sync?gdpr=A&gdpr_consent=B&us_privacy=C&redir=%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 154, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/vrtcal/usersync.go b/adapters/vrtcal/usersync.go index 04e52955033..8a6ddc0ca26 100644 --- a/adapters/vrtcal/usersync.go +++ b/adapters/vrtcal/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewVrtcalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("vrtcal", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("vrtcal", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/vrtcal/usersync_test.go b/adapters/vrtcal/usersync_test.go index 26e5838dbe3..7f8ca220a96 100644 --- a/adapters/vrtcal/usersync_test.go +++ b/adapters/vrtcal/usersync_test.go @@ -25,7 +25,6 @@ func TestVrtcalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://usync-prebid.vrtcal.com/s?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "vrtcal", syncer.FamilyName()) } diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go index 3ee9a3fdfb5..90507e31161 100644 --- a/adapters/yieldlab/usersync.go +++ b/adapters/yieldlab/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldlab", 70, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldlab", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go index 3892c16bf05..eabd46f6dce 100644 --- a/adapters/yieldlab/usersync_test.go +++ b/adapters/yieldlab/usersync_test.go @@ -21,6 +21,5 @@ func TestYieldlabSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 70, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/yieldmo/usersync.go b/adapters/yieldmo/usersync.go index 041e7e8f073..d06caa90c0b 100644 --- a/adapters/yieldmo/usersync.go +++ b/adapters/yieldmo/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewYieldmoSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldmo", 173, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldmo", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldmo/usersync_test.go b/adapters/yieldmo/usersync_test.go index 598710ec742..5d12c63e4aa 100644 --- a/adapters/yieldmo/usersync_test.go +++ b/adapters/yieldmo/usersync_test.go @@ -25,6 +25,5 @@ func TestYieldmoSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.yieldmo.com/pbsync?gdpr=0&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 173, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/yieldone/usersync.go b/adapters/yieldone/usersync.go index bc9d1b3235b..4d5d8283a68 100644 --- a/adapters/yieldone/usersync.go +++ b/adapters/yieldone/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewYieldoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldone", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldone", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldone/usersync_test.go b/adapters/yieldone/usersync_test.go index 902f3b66b34..c4d0fee92dd 100644 --- a/adapters/yieldone/usersync_test.go +++ b/adapters/yieldone/usersync_test.go @@ -25,6 +25,5 @@ func TestYieldoneSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/zeroclickfraud/usersync.go b/adapters/zeroclickfraud/usersync.go index 833524e4b3e..41c589818ca 100644 --- a/adapters/zeroclickfraud/usersync.go +++ b/adapters/zeroclickfraud/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("zeroclickfraud", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("zeroclickfraud", temp, adapters.SyncTypeIframe) } diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 4af8a97cd32..9eff288f64f 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -19,6 +19,7 @@ type BidderInfo struct { Capabilities *CapabilitiesInfo `yaml:"capabilities"` ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed"` Debug *DebugInfo `yaml:"debug,omitempty"` + GVLVendorID uint16 `yaml:"gvlVendorID,omitempty"` } // MaintainerInfo is the support email address for a bidder. @@ -43,13 +44,13 @@ type DebugInfo struct { } // LoadBidderInfoFromDisk parses all static/bidder-info/{bidder}.yaml files from the file system. -func LoadBidderInfoFromDisk(path string, adapterConfigs map[string]Adapter, bidders []string) (map[string]BidderInfo, error) { +func LoadBidderInfoFromDisk(path string, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { reader := infoReaderFromDisk{path} return loadBidderInfo(reader, adapterConfigs, bidders) } -func loadBidderInfo(r infoReader, adapterConfigs map[string]Adapter, bidders []string) (map[string]BidderInfo, error) { - infos := map[string]BidderInfo{} +func loadBidderInfo(r infoReader, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { + infos := BidderInfos{} for _, bidder := range bidders { data, err := r.Read(bidder) @@ -86,3 +87,15 @@ func (r infoReaderFromDisk) Read(bidder string) ([]byte, error) { path := fmt.Sprintf("%v/%v.yaml", r.path, bidder) return ioutil.ReadFile(path) } + +// ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. Disabled +// bidders are omitted from the result. +func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 { + m := make(map[openrtb_ext.BidderName]uint16, len(infos)) + for name, info := range infos { + if info.Enabled && info.GVLVendorID != 0 { + m[openrtb_ext.BidderName(name)] = info.GVLVendorID + } + } + return m +} diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 2d9d53ec369..62a0b853abd 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -13,6 +13,7 @@ const testInfoFilesPath = "./test/bidder-info" const testYAML = ` maintainer: email: "some-email@domain.com" +gvlVendorID: 42 capabilities: app: mediaTypes: @@ -36,12 +37,13 @@ func TestLoadBidderInfoFromDisk(t *testing.T) { t.Fatal(err) } - expected := map[string]BidderInfo{ + expected := BidderInfos{ bidder: { Enabled: true, Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, + GVLVendorID: 42, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, @@ -63,7 +65,7 @@ func TestLoadBidderInfo(t *testing.T) { givenConfigs map[string]Adapter givenContent string givenError error - expectedInfo map[string]BidderInfo + expectedInfo BidderInfos expectedError string }{ { @@ -76,6 +78,7 @@ func TestLoadBidderInfo(t *testing.T) { Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, + GVLVendorID: 42, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, @@ -97,6 +100,7 @@ func TestLoadBidderInfo(t *testing.T) { Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, + GVLVendorID: 42, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, @@ -118,6 +122,7 @@ func TestLoadBidderInfo(t *testing.T) { Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, + GVLVendorID: 42, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, @@ -139,6 +144,7 @@ func TestLoadBidderInfo(t *testing.T) { Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, + GVLVendorID: 42, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, @@ -186,3 +192,19 @@ type fakeInfoReader struct { func (r fakeInfoReader) Read(bidder string) ([]byte, error) { return []byte(r.content), r.err } + +func TestToGVLVendorIDMap(t *testing.T) { + givenBidderInfos := BidderInfos{ + "bidderA": BidderInfo{Enabled: true, GVLVendorID: 0}, + "bidderB": BidderInfo{Enabled: true, GVLVendorID: 100}, + "bidderC": BidderInfo{Enabled: false, GVLVendorID: 0}, + "bidderD": BidderInfo{Enabled: false, GVLVendorID: 200}, + } + + expectedGVLVendorIDMap := map[openrtb_ext.BidderName]uint16{ + "bidderB": 100, + } + + result := givenBidderInfos.ToGVLVendorIDMap() + assert.Equal(t, expectedGVLVendorIDMap, result) +} diff --git a/config/test/bidder-info/someBidder.yaml b/config/test/bidder-info/someBidder.yaml index b5216f0fe46..232b73d0aac 100644 --- a/config/test/bidder-info/someBidder.yaml +++ b/config/test/bidder-info/someBidder.yaml @@ -1,5 +1,6 @@ maintainer: email: "some-email@domain.com" +gvlVendorID: 42 capabilities: app: mediaTypes: diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index fc98608ef9f..87c98f23ad1 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -462,8 +462,3 @@ func (s fakeSyncer) FamilyName() string { func (s fakeSyncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.UsersyncInfo, error) { return nil, nil } - -// GDPRVendorID implements the Usersyncer interface with a no-op. -func (s fakeSyncer) GDPRVendorID() uint16 { - return 0 -} diff --git a/router/router.go b/router/router.go index 7a088bbfc68..0920f1d61b6 100644 --- a/router/router.go +++ b/router/router.go @@ -217,7 +217,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R // Metrics engine r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList) db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) - // todo(zachbadgett): better shutdown r.Shutdown = shutdown if err := loadDataCache(cfg, db); err != nil { @@ -246,7 +245,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } syncers := usersyncers.NewSyncerMap(cfg) - gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(syncers), generalHttpClient) + gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() + gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) exchanges = newExchangeMap(cfg) cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index e3eea765556..044cfe140b2 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,5 +1,6 @@ maintainer: email: "headerbidding@33across.com" +gvlVendorID: 58 capabilities: site: mediaTypes: diff --git a/static/bidder-info/acuityads.yaml b/static/bidder-info/acuityads.yaml index 9da1446d918..9539e36b91e 100644 --- a/static/bidder-info/acuityads.yaml +++ b/static/bidder-info/acuityads.yaml @@ -1,5 +1,6 @@ maintainer: email: "integrations@acuityads.com" +gvlVendorID: 231 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 4dce10b9af8..461714ac44d 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1,5 +1,6 @@ maintainer: email: "scope.sspp@adform.com" +gvlVendorID: 50 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index 26f8c322ddc..a78b3cde498 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-dev@adkernel.com" +gvlVendorID: 14 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adkernelAdn.yaml b/static/bidder-info/adkernelAdn.yaml index fd47367db7c..1f54f8e5a8f 100644 --- a/static/bidder-info/adkernelAdn.yaml +++ b/static/bidder-info/adkernelAdn.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-dev@adkernel.com" +gvlVendorID: 14 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adman.yaml b/static/bidder-info/adman.yaml index 932ef2e4242..2db7c07584c 100644 --- a/static/bidder-info/adman.yaml +++ b/static/bidder-info/adman.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@admanmedia.com" +gvlVendorID: 149 capabilities: app: mediaTypes: diff --git a/static/bidder-info/admixer.yaml b/static/bidder-info/admixer.yaml index 64ad2024058..9faf0eb3a3e 100644 --- a/static/bidder-info/admixer.yaml +++ b/static/bidder-info/admixer.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@admixer.net" +gvlVendorID: 511 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 2f31fe92eaf..b3a23718a5a 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -1,5 +1,6 @@ maintainer: email: "aoteam@gemius.com" +gvlVendorID: 328 capabilities: site: mediaTypes: diff --git a/static/bidder-info/adpone.yaml b/static/bidder-info/adpone.yaml index 969983a12f6..7c5b473770b 100644 --- a/static/bidder-info/adpone.yaml +++ b/static/bidder-info/adpone.yaml @@ -1,5 +1,6 @@ maintainer: email: "tech@adpone.com" +gvlVendorID: 799 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index 7a20d52b266..30f55ea912e 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -1,5 +1,6 @@ maintainer: email: "hb@adtelligent.com" +gvlVendorID: 410 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml index 3f35aa5b038..d9b23d6c1a1 100644 --- a/static/bidder-info/adyoulike.yaml +++ b/static/bidder-info/adyoulike.yaml @@ -1,5 +1,6 @@ maintainer: email: "core@adyoulike.com" +gvlVendorID: 259 modifyingVastXmlAllowed: true capabilities: site: diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml index 3e20d2095f6..f9fdfbb4a41 100644 --- a/static/bidder-info/amx.yaml +++ b/static/bidder-info/amx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@amxrtb.com" +gvlVendorID: 737 capabilities: site: mediaTypes: diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml index f1e7ca23cfb..f2f4a1266df 100644 --- a/static/bidder-info/appnexus.yaml +++ b/static/bidder-info/appnexus.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-server@xandr.com" +gvlVendorID: 32 capabilities: app: mediaTypes: diff --git a/static/bidder-info/avocet.yaml b/static/bidder-info/avocet.yaml index ea98982d69c..42147c96ebf 100644 --- a/static/bidder-info/avocet.yaml +++ b/static/bidder-info/avocet.yaml @@ -1,5 +1,6 @@ maintainer: email: "developers@avocet.io" +gvlVendorID: 63 capabilities: app: mediaTypes: diff --git a/static/bidder-info/beachfront.yaml b/static/bidder-info/beachfront.yaml index 335545ff1ee..06991698090 100644 --- a/static/bidder-info/beachfront.yaml +++ b/static/bidder-info/beachfront.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@beachfront.com" +gvlVendorID: 335 capabilities: app: mediaTypes: diff --git a/static/bidder-info/beintoo.yaml b/static/bidder-info/beintoo.yaml index fcca29220cf..905d89a44c4 100644 --- a/static/bidder-info/beintoo.yaml +++ b/static/bidder-info/beintoo.yaml @@ -1,5 +1,6 @@ maintainer: email: "adops@beintoo.com" +gvlVendorID: 618 capabilities: site: mediaTypes: diff --git a/static/bidder-info/between.yaml b/static/bidder-info/between.yaml index d317d275c59..71bd8ba6256 100644 --- a/static/bidder-info/between.yaml +++ b/static/bidder-info/between.yaml @@ -1,5 +1,6 @@ maintainer: email: "buying@betweenx.com" +gvlVendorID: 724 capabilities: site: mediaTypes: diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml index f913be6da8c..a2ea0c74b77 100644 --- a/static/bidder-info/brightroll.yaml +++ b/static/bidder-info/brightroll.yaml @@ -1,5 +1,6 @@ maintainer: email: "dsp-supply-prebid@verizonmedia.com" +gvlVendorID: 25 capabilities: app: mediaTypes: diff --git a/static/bidder-info/connectad.yaml b/static/bidder-info/connectad.yaml index 1b3e593d78d..fa0ae4520fc 100644 --- a/static/bidder-info/connectad.yaml +++ b/static/bidder-info/connectad.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@connectad.io" +gvlVendorID: 138 capabilities: app: mediaTypes: diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index f12ab2b4016..e9b5f72623c 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -1,5 +1,6 @@ maintainer: email: "naffis@consumable.com" +gvlVendorID: 591 capabilities: app: mediaTypes: diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index 017f0e0c57e..aa3d3822802 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -1,5 +1,6 @@ maintainer: email: "CNVR_PublisherIntegration@conversantmedia.com" +gvlVendorID: 24 capabilities: app: mediaTypes: diff --git a/static/bidder-info/deepintent.yaml b/static/bidder-info/deepintent.yaml index 490e161c8d2..a8f17a55d6f 100644 --- a/static/bidder-info/deepintent.yaml +++ b/static/bidder-info/deepintent.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebiddev@deepintent.com" +gvlVendorID: 541 capabilities: app: mediaTypes: diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml index 3ecb452f7f6..d29d699daeb 100644 --- a/static/bidder-info/dmx.yaml +++ b/static/bidder-info/dmx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@districtm.net" +gvlVendorID: 144 capabilities: site: mediaTypes: diff --git a/static/bidder-info/emx_digital.yaml b/static/bidder-info/emx_digital.yaml index 49a068093eb..ec0d090fb4c 100644 --- a/static/bidder-info/emx_digital.yaml +++ b/static/bidder-info/emx_digital.yaml @@ -1,5 +1,6 @@ maintainer: email: "adops@emxdigital.com" +gvlVendorID: 183 capabilities: site: mediaTypes: diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index 57c359e451d..fd08367acc7 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -1,5 +1,6 @@ maintainer: email: "tech@engagebdr.com" +gvlVendorID: 62 capabilities: app: mediaTypes: diff --git a/static/bidder-info/eplanning.yaml b/static/bidder-info/eplanning.yaml index 907523d3eb3..ab0b7609dbb 100644 --- a/static/bidder-info/eplanning.yaml +++ b/static/bidder-info/eplanning.yaml @@ -1,5 +1,6 @@ maintainer: email: "producto@e-planning.net" +gvlVendorID: 90 capabilities: app: mediaTypes: diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml index c3ed3ff10e4..0cfd495762f 100644 --- a/static/bidder-info/gamoshi.yaml +++ b/static/bidder-info/gamoshi.yaml @@ -1,5 +1,6 @@ maintainer: email: "dev@gamoshi.com" +gvlVendorID: 644 capabilities: app: mediaTypes: diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index 325421a2c05..31c7b7320e3 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -1,5 +1,6 @@ maintainer: email: "grid-tech@themediagrid.com" +gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index 6ba563b4c1c..bfefe63ab40 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@gumgum.com" +gvlVendorID: 61 capabilities: site: mediaTypes: diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index 7983cbc1a61..6c31d26826a 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -1,5 +1,6 @@ maintainer: email: "hb@azerion.com" +gvlVendorID: 253 capabilities: app: mediaTypes: diff --git a/static/bidder-info/invibes.yaml b/static/bidder-info/invibes.yaml index 1432529787e..45184fb9f65 100644 --- a/static/bidder-info/invibes.yaml +++ b/static/bidder-info/invibes.yaml @@ -1,5 +1,6 @@ maintainer: email: "system_operations@invibes.com" +gvlVendorID: 436 capabilities: site: mediaTypes: diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index 2f00ceb952f..1e89c72e5bb 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -1,5 +1,6 @@ maintainer: email: "pdu-supply-prebid@indexexchange.com" +gvlVendorID: 10 capabilities: app: mediaTypes: diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/lifestreet.yaml index 139d55990b9..34dc4eca2d9 100644 --- a/static/bidder-info/lifestreet.yaml +++ b/static/bidder-info/lifestreet.yaml @@ -1,5 +1,6 @@ maintainer: email: "mobile.tech@lifestreet.com" +gvlVendorID: 67 capabilities: app: mediaTypes: diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index 112f67fe556..98611402905 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@mediafuse.com" +gvlVendorID: 411 capabilities: app: mediaTypes: diff --git a/static/bidder-info/mgid.yaml b/static/bidder-info/mgid.yaml index f8ba6db60b1..bddb8b8598e 100644 --- a/static/bidder-info/mgid.yaml +++ b/static/bidder-info/mgid.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@mgid.com" +gvlVendorID: 358 capabilities: app: mediaTypes: diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml index 244e7602950..d199e9e8ff5 100644 --- a/static/bidder-info/nanointeractive.yaml +++ b/static/bidder-info/nanointeractive.yaml @@ -1,5 +1,6 @@ maintainer: email: "development@nanointeractive.com" +gvlVendorID: 72 capabilities: app: mediaTypes: diff --git a/static/bidder-info/nobid.yaml b/static/bidder-info/nobid.yaml index 51a55de46bc..89f2a28abcd 100644 --- a/static/bidder-info/nobid.yaml +++ b/static/bidder-info/nobid.yaml @@ -1,5 +1,6 @@ maintainer: email: "developers@nobid.io" +gvlVendorID: 816 capabilities: site: mediaTypes: diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml index fedaabf9949..697ef04d5a1 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -1,5 +1,6 @@ maintainer: email: devops@onetag.com +gvlVendorID: 241 capabilities: app: mediaTypes: diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index 96ef8616c96..709f3db0147 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@openx.com" +gvlVendorID: 69 capabilities: app: mediaTypes: diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 4009d439352..45c0418af8a 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -1,5 +1,6 @@ maintainer: email: "header-bidding@pubmatic.com" +gvlVendorID: 76 capabilities: app: mediaTypes: diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 056a0bf3d7c..bda03efd99c 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -1,5 +1,6 @@ maintainer: email: "ExchangeTeam@pulsepoint.com" +gvlVendorID: 81 capabilities: app: mediaTypes: diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml index 89da6cfea35..852344db3e3 100644 --- a/static/bidder-info/rhythmone.yaml +++ b/static/bidder-info/rhythmone.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@rhythmone.com" +gvlVendorID: 36 capabilities: app: mediaTypes: diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index f15af6ca2e1..12744fdc75e 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@rtbhouse.com" +gvlVendorID: 16 capabilities: site: mediaTypes: diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index 40848e3ec25..0f19ddb9627 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,5 +1,6 @@ maintainer: email: "header-bidding@rubiconproject.com" +gvlVendorID: 52 capabilities: app: mediaTypes: diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 09530be508c..45dceb94d22 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -1,5 +1,6 @@ maintainer: email: "pubgrowth.engineering@sharethrough.com" +gvlVendorID: 80 capabilities: app: mediaTypes: diff --git a/static/bidder-info/smartadserver.yaml b/static/bidder-info/smartadserver.yaml index 626b7dac00d..f22c7149ff7 100644 --- a/static/bidder-info/smartadserver.yaml +++ b/static/bidder-info/smartadserver.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@smartadserver.com" +gvlVendorID: 45 capabilities: app: mediaTypes: diff --git a/static/bidder-info/somoaudience.yaml b/static/bidder-info/somoaudience.yaml index 17156c18039..83b64e5c58e 100644 --- a/static/bidder-info/somoaudience.yaml +++ b/static/bidder-info/somoaudience.yaml @@ -1,5 +1,6 @@ maintainer: email: "publishers@somoaudience.com" +gvlVendorID: 341 capabilities: app: mediaTypes: diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index 6d39319a9f5..22a0a158306 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -1,5 +1,6 @@ maintainer: email: "apex.prebid@sonobi.com" +gvlVendorID: 104 capabilities: site: mediaTypes: diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index c62d61df8d8..4c6251bdf57 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -1,5 +1,6 @@ maintainer: email: "sovrnoss@sovrn.com" +gvlVendorID: 13 capabilities: app: mediaTypes: diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml index 527959b75d1..eb655aa6a0c 100644 --- a/static/bidder-info/tappx.yaml +++ b/static/bidder-info/tappx.yaml @@ -1,5 +1,6 @@ maintainer: email: "tappx@tappx.com" +gvlVendorID: 628 capabilities: app: mediaTypes: diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml index 43e8707a17b..736fb9720b3 100644 --- a/static/bidder-info/telaria.yaml +++ b/static/bidder-info/telaria.yaml @@ -1,5 +1,6 @@ maintainer: email: "github@telaria.com" +gvlVendorID: 202 capabilities: app: mediaTypes: diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 2b9ff8d5675..fe0ad8b2203 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@triplelift.com" +gvlVendorID: 28 capabilities: app: mediaTypes: diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index abe91659935..40d9be8f294 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@triplelift.com" +gvlVendorID: 28 capabilities: app: mediaTypes: diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml index 9ce52c1116a..6cfdd9fa465 100644 --- a/static/bidder-info/trustx.yaml +++ b/static/bidder-info/trustx.yaml @@ -1,5 +1,6 @@ maintainer: email: "grid-tech@themediagrid.com" +gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/ucfunnel.yaml b/static/bidder-info/ucfunnel.yaml index 288b0b3f1b8..e6be68a0261 100644 --- a/static/bidder-info/ucfunnel.yaml +++ b/static/bidder-info/ucfunnel.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@ucfunnel.com" +gvlVendorID: 607 capabilities: app: mediaTypes: diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 01ed3774118..e31ea600b3e 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,5 +1,6 @@ maintainer: email: "adspaces@unrulygroup.com" +gvlVendorID: 162 capabilities: site: mediaTypes: diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index c00f2158d4b..0b4642fcb6a 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -1,5 +1,6 @@ maintainer: email: "dsp-supply-prebid@verizonmedia.com" +gvlVendorID: 25 capabilities: app: mediaTypes: diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index b6a16e4c2d0..789ce478bea 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -1,5 +1,6 @@ maintainer: email: "supply.partners@yoc.com" +gvlVendorID: 154 capabilities: site: mediaTypes: diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml index 654e6c749cb..3030d8a1d42 100644 --- a/static/bidder-info/yieldlab.yaml +++ b/static/bidder-info/yieldlab.yaml @@ -1,5 +1,6 @@ maintainer: email: "solutions@yieldlab.de" +gvlVendorID: 70 capabilities: site: mediaTypes: diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index 64cda519acd..b1385acbebc 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@yieldmo.com" +gvlVendorID: 173 capabilities: app: mediaTypes: diff --git a/usersync/usersync.go b/usersync/usersync.go index 236acbe73a3..dffc1832f01 100644 --- a/usersync/usersync.go +++ b/usersync/usersync.go @@ -14,16 +14,6 @@ type Usersyncer interface { // TODO #362: when the appnexus usersyncer is consistent, delete this and use the key // of NewSyncerMap() here instead. FamilyName() string - - // GDPRVendorID returns the ID in the IAB Global Vendor List which refers to this Bidder. - // - // The Global Vendor list can be found here: https://vendor-list.consensu.org/vendorlist.json - // Bidders can register for the list here: https://register.consensu.org/ - // - // If you're not on the list, this should return 0. If cookie sync requests have GDPR consent info, - // or the Prebid Server host company configures its deploy to be "cautious" when no GDPR info exists - // in the request, it will _not_ sync user IDs with you. - GDPRVendorID() uint16 } type UsersyncInfo struct { diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 9727a2d4e22..5b2e80463ac 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -136,31 +136,3 @@ func TestNewSyncerMap(t *testing.T) { } } } - -// Bidders may have an ID on the IAB-maintained global vendor list. -// This makes sure that we don't have conflicting IDs among Bidders in our project, -// since that's almost certainly a bug. -func TestVendorIDUniqueness(t *testing.T) { - cfg := &config.Configuration{} - syncers := NewSyncerMap(cfg) - - idMap := make(map[uint16]openrtb_ext.BidderName, len(syncers)) - for name, syncer := range syncers { - id := syncer.GDPRVendorID() - if id == 0 { - continue - } - - if oldName, ok := idMap[id]; ok { - t.Errorf("GDPR VendorList ID %d used by both %s and %s. These must be unique.", id, oldName, name) - } - idMap[id] = name - } -} - -func assertStringsMatch(t *testing.T, expected string, actual string) { - t.Helper() - if expected != actual { - t.Errorf("Expected %s, got %s", expected, actual) - } -} From 49d84cc77b808b336e55374e775583470801e17a Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 11 Mar 2021 02:49:42 +0100 Subject: [PATCH 341/603] Improve Digital adapter: add support for native ads (#1746) --- adapters/improvedigital/improvedigital.go | 4 + .../improvedigitaltest/exemplary/native.json | 161 ++++++++++++++++++ .../params/race/native.json | 12 ++ .../supplemental/native.json | 64 ------- static/bidder-info/improvedigital.yaml | 2 + 5 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 adapters/improvedigital/improvedigitaltest/exemplary/native.json create mode 100644 adapters/improvedigital/improvedigitaltest/params/race/native.json delete mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/native.json diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index 0d5c043ef60..b1cc3640714 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -114,6 +114,10 @@ func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, return openrtb_ext.BidTypeVideo, nil } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + return "", &errortypes.BadServerResponse{ Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), } diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/native.json b/adapters/improvedigital/improvedigitaltest/exemplary/native.json new file mode 100644 index 00000000000..3309b35a753 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/exemplary/native.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { "asi": "example.com", "hp": 1, "sid": "1234abc" } + ] + } + } + }, + "tmax": 1000, + "imp": [ + { + "id": "native", + "ext": { "improvedigital": { "placementId": 1234 } }, + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", + "ver": "1.2" + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { "id": "pubid" }, + "page": "example.com" + }, + "device": { "w": 1581, "h": 922, "ip": "1.1.1.1" }, + "regs": { "ext": { "gdpr": 0 } }, + "user": { + "ext": { + "consent": "XYZ" + } + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "imp": [ + { + "id": "native", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", + "ver": "1.2" + }, + "ext": { "improvedigital": { "placementId": 1234 } } + } + ], + "site": { + "page": "example.com", + "publisher": { "id": "pubid" } + }, + "device": { + "ip": "1.1.1.1", + "h": 922, + "w": 1581 + }, + "user": { + "ext": { + "consent": "XYZ" + } + }, + "tmax": 1000, + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "hp": 1, + "sid": "1234abc" + } + ] + } + } + }, + "regs": { "ext": { "gdpr": 0 } }, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + }, + "crid": "14065", + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "price": 0.01, + "adm": "{\"assets\":[{\"title\":{\"text\":\"Luxury Mars Cruises\"},\"id\":1},{\"id\":2,\"data\":{\"type\":2,\"value\":\"Visit the planet in a luxury spaceship.\"}},{\"id\":3,\"data\":{\"type\":12,\"value\":\"Book today\"}},{\"id\":4,\"img\":{\"h\":250,\"type\":3,\"url\":\"http://hb.improvedigital.com/creatives/display/300x250.jpg\",\"w\":300}},{\"id\":6,\"data\":{\"type\":3,\"value\":\"3.8\"}},{\"id\":7,\"data\":{\"type\":6,\"value\":\"FREE\"}}],\"link\":{\"clicktrackers\":[\"http://localhost.localdomain/click/\"],\"url\":\"http://www.iponweb.com/careers/\"}}", + "adomain": ["example.com"], + "impid": "native", + "cid": "25076" + } + ], + "seat": "improvedigital" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "impid": "native", + "price": 0.01, + "adm": "{\"assets\":[{\"title\":{\"text\":\"Luxury Mars Cruises\"},\"id\":1},{\"id\":2,\"data\":{\"type\":2,\"value\":\"Visit the planet in a luxury spaceship.\"}},{\"id\":3,\"data\":{\"type\":12,\"value\":\"Book today\"}},{\"id\":4,\"img\":{\"h\":250,\"type\":3,\"url\":\"http://hb.improvedigital.com/creatives/display/300x250.jpg\",\"w\":300}},{\"id\":6,\"data\":{\"type\":3,\"value\":\"3.8\"}},{\"id\":7,\"data\":{\"type\":6,\"value\":\"FREE\"}}],\"link\":{\"clicktrackers\":[\"http://localhost.localdomain/click/\"],\"url\":\"http://www.iponweb.com/careers/\"}}", + "adomain": ["example.com"], + "cid": "25076", + "crid": "14065", + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/improvedigital/improvedigitaltest/params/race/native.json b/adapters/improvedigital/improvedigitaltest/params/race/native.json new file mode 100644 index 00000000000..0de1d580215 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/params/race/native.json @@ -0,0 +1,12 @@ +{ + "placementId": 123, + "publisherId": 321, + "placementKey": "uniq_name", + "size": { + "w": 100, + "h": 100 + }, + "keyValues": { + "testKey1": ["testValueA", "testValueB"] + } +} diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/native.json b/adapters/improvedigital/improvedigitaltest/supplemental/native.json deleted file mode 100644 index 3090700a275..00000000000 --- a/adapters/improvedigital/improvedigitaltest/supplemental/native.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "native": { - "ver": "1.1" - }, - "ext": { - "bidder": { - "placementId": 13245 - } - } - }] - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/pbs", - "body": { - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "native": { - "request": "", - "ver": "1.1" - }, - "ext": { - "bidder": { - "placementId": 13245 - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "improvedigital", - "bid": [{ - "id": "randomid", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-native", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - }] - }], - "cur": "USD" - } - } - }], - - "expectedMakeBidsErrors": [ - { - "value": "Unknown impression type for ID: \"test-imp-id\"", - "comparison": "literal" - } - ] -} diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index 6c31d26826a..f7fea4a8402 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -6,7 +6,9 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native From 849a311070759dc0180234313c9a9b6eb56a5fda Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 11 Mar 2021 10:04:56 -0500 Subject: [PATCH 342/603] Add Support For SkAdN + Refactor Split Imps (#1741) --- endpoints/openrtb2/auction.go | 33 +- .../valid-whole/exemplary/skadn.json | 48 +++ ...ata-imp-ext-multiple-prebid-bidders.json } | 2 - ...stpartydata-imp-ext-one-prebid-bidder.json | 1 - exchange/utils.go | 153 ++++---- exchange/utils_test.go | 364 ++++++++++++++++++ openrtb_ext/bidders.go | 2 +- openrtb_ext/device.go | 3 + openrtb_ext/request.go | 9 +- 9 files changed, 505 insertions(+), 110 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json rename exchange/exchangetest/{firstpartydata-imp-ext-multiple-prebid-bidders.json => firstpartydata-imp-ext-multiple-prebid-bidders.json } (98%) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 290b06cb7b2..e4567bbcd29 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -830,19 +830,15 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st return []error{err} } - // Also accept bidder exts within imp[...].ext.prebid.bidder - // NOTE: This is not part of the official API yet, so we are not expecting clients - // to migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} - // at this time - // https://github.com/prebid/prebid-server/pull/846#issuecomment-476352224 - if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { - var prebidExt openrtb_ext.ExtImpPrebid - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - for bidder, ext := range prebidExt.Bidder { + // Prefer bidder params from request.imp.ext.prebid.bidder.BIDDER over request.imp.ext.BIDDER + // to avoid confusion beteween prebid specific adapter config and other ext protocols. + if extPrebidJSON, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { + var extPrebid openrtb_ext.ExtImpPrebid + if err := json.Unmarshal(extPrebidJSON, &extPrebid); err == nil && extPrebid.Bidder != nil { + for bidder, ext := range extPrebid.Bidder { if ext == nil { continue } - bidderExts[bidder] = ext } } @@ -893,13 +889,18 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st return errL } +// isBidderToValidate determines if the bidder name in request.imp[].prebid should be validated. func isBidderToValidate(bidder string) bool { - // PrebidExtKey is a special case for the prebid config section and is not considered a bidder. - - // FirstPartyDataContextExtKey is a special case for the first party data context section - // and is not considered a bidder. - - return bidder != openrtb_ext.PrebidExtKey && bidder != openrtb_ext.FirstPartyDataContextExtKey + switch openrtb_ext.BidderName(bidder) { + case openrtb_ext.BidderReservedContext: + return false + case openrtb_ext.BidderReservedPrebid: + return false + case openrtb_ext.BidderReservedSKAdN: + return false + default: + return true + } } func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json new file mode 100644 index 00000000000..e238f3c07c7 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json @@ -0,0 +1,48 @@ +{ + "description": "The imp.ext.skadn field is valid for Apple's SKAdNetwork and should be exempt from bidder name validation.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "skadn": { + "anyMember": "anyValue" + } + } + }] + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json similarity index 98% rename from exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json rename to exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index d62afccf426..37654c454bc 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -61,7 +61,6 @@ "bidder": { "placementId": 1 }, - "prebid": {}, "context": { "data": { "keywords": "prebid server example" @@ -112,7 +111,6 @@ "siteId": 2, "zoneId": 3 }, - "prebid": {}, "context": { "data": { "keywords": "prebid server example" diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 1610b9ea47e..c931b0b9ee3 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -56,7 +56,6 @@ "bidder": { "placementId": 1 }, - "prebid": {}, "context": { "data": { "keywords": "prebid server example" diff --git a/exchange/utils.go b/exchange/utils.go index 8d4c0facd3b..4ca02149453 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -59,8 +59,9 @@ func cleanOpenRTBRequests(ctx context.Context, usersyncIfAmbiguous bool, privacyConfig config.Privacy) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { - impsByBidder, errs := splitImps(req.BidRequest.Imp) - if len(errs) > 0 { + impsByBidder, err := splitImps(req.BidRequest.Imp) + if err != nil { + errs = []error{err} return } @@ -327,106 +328,99 @@ func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key. // // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them. -func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { - impExts, err := parseImpExts(imps) - if err != nil { - return nil, []error{err} - } +func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, error) { + bidderImps := make(map[string][]openrtb.Imp) - splitImps := make(map[string][]openrtb.Imp, len(imps)) - var errList []error + for i, imp := range imps { + var impExt map[string]json.RawMessage + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + return nil, fmt.Errorf("invalid json for imp[%d]: %v", i, err) + } - for i := 0; i < len(imps); i++ { - imp := imps[i] - impExt := impExts[i] + var impExtPrebid map[string]json.RawMessage + if impExtPrebidJSON, exists := impExt[openrtb_ext.PrebidExtKey]; exists { + // validation already performed by impExt unmarshal. no error is possible here, proven by tests. + json.Unmarshal(impExtPrebidJSON, &impExtPrebid) + } - var firstPartyDataContext json.RawMessage - if context, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { - firstPartyDataContext = context + var impExtPrebidBidder map[string]json.RawMessage + if impExtPrebidBidderJSON, exists := impExtPrebid[openrtb_ext.PrebidExtBidderKey]; exists { + // validation already performed by impExt unmarshal. no error is possible here, proven by tests. + json.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) } - rawPrebidExt, ok := impExt[openrtb_ext.PrebidExtKey] + sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) + if err != nil { + return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err) + } - if ok { - var prebidExt openrtb_ext.ExtImpPrebid + for bidder, bidderExt := range extractBidderExts(impExt, impExtPrebidBidder) { + impCopy := imp - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { - errList = append(errList, errs...) - } + sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt - continue + impExtJSON, err := json.Marshal(sanitizedImpExt) + if err != nil { + return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: cannot marshal ext: %v", i, err) } - } + impCopy.Ext = impExtJSON - if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { - errList = append(errList, errs...) + bidderImps[bidder] = append(bidderImps[bidder], impCopy) } } - return splitImps, nil + return bidderImps, nil } -// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid", "context", and bidder params exist. -// It will not mutate the input imp. -// This function will write the new imps to the output map passed in -func sanitizedImpCopy(imp *openrtb.Imp, - bidderExts map[string]json.RawMessage, - rawPrebidExt json.RawMessage, - firstPartyDataContext json.RawMessage, - out *map[string][]openrtb.Imp) []error { - - var prebidExt map[string]json.RawMessage - var errs []error - - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil { - // Remove the entire bidder field. We will already have the content we need in bidderExts. We - // don't want to include other demand partners' bidder params in the sanitized imp. - if _, hasBidderField := prebidExt["bidder"]; hasBidderField { - delete(prebidExt, "bidder") +func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { + sanitizedImpExt := make(map[string]json.RawMessage, 3) - var err error - if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { - errs = append(errs, err) - } + delete(impExtPrebid, openrtb_ext.PrebidExtBidderKey) + if len(impExtPrebid) > 0 { + if impExtPrebidJSON, err := json.Marshal(impExtPrebid); err == nil { + sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON + } else { + return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) } } - for bidder, ext := range bidderExts { - if bidder == openrtb_ext.PrebidExtKey || bidder == openrtb_ext.FirstPartyDataContextExtKey { - continue - } + if v, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { + sanitizedImpExt[openrtb_ext.FirstPartyDataContextExtKey] = v + } - impCopy := *imp - newExt := make(map[string]json.RawMessage, 3) + if v, exists := impExt[openrtb_ext.SKAdNExtKey]; exists { + sanitizedImpExt[openrtb_ext.SKAdNExtKey] = v + } - newExt["bidder"] = ext + return sanitizedImpExt, nil +} - if rawPrebidExt != nil { - newExt[openrtb_ext.PrebidExtKey] = rawPrebidExt - } +func extractBidderExts(impExt, impExtPrebidBidders map[string]json.RawMessage) map[string]json.RawMessage { + bidderExts := make(map[string]json.RawMessage) - if len(firstPartyDataContext) > 0 { - newExt[openrtb_ext.FirstPartyDataContextExtKey] = firstPartyDataContext - } + // prefer imp.ext.prebid.bidder.BIDDER + for bidder, bidderExt := range impExtPrebidBidders { + bidderExts[bidder] = bidderExt + } - rawExt, err := json.Marshal(newExt) - if err != nil { - errs = append(errs, err) + // fallback to imp.BIDDER + for bidder, bidderExt := range impExt { + if isSpecialField(bidder) { continue } - impCopy.Ext = rawExt - - otherImps, _ := (*out)[bidder] - (*out)[bidder] = append(otherImps, impCopy) + if _, exists := bidderExts[bidder]; !exists { + bidderExts[bidder] = bidderExt + } } - if len(errs) > 0 { - return errs - } + return bidderExts +} - return nil +func isSpecialField(bidder string) bool { + return bidder == openrtb_ext.FirstPartyDataContextExtKey || + bidder == openrtb_ext.SKAdNExtKey || + bidder == openrtb_ext.PrebidExtKey } // prepareUser changes req.User so that it's ready for the given bidder. @@ -564,21 +558,6 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN return openrtb_ext.BidderName(bidder) } -// parseImpExts does a partial-unmarshal of the imp[].Ext field. -// The keys in the returned map are expected to be "prebid", "context", CoreBidderNames, or Aliases for this request. -func parseImpExts(imps []openrtb.Imp) ([]map[string]json.RawMessage, error) { - exts := make([]map[string]json.RawMessage, len(imps)) - // Loop over every impression in the request - for i := 0; i < len(imps); i++ { - // Unpack each set of extensions found in the Imp array - err := json.Unmarshal(imps[i].Ext, &exts[i]) - if err != nil { - return nil, fmt.Errorf("Error unpacking extensions for Imp[%d]: %s", i, err.Error()) - } - } - return exts, nil -} - // parseAliases parses the aliases from the BidRequest func parseAliases(orig *openrtb.BidRequest) (map[string]string, []error) { var aliases map[string]string diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 0407b3c5e0e..f089d70523b 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -55,6 +55,370 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest, } } +func TestSplitImps(t *testing.T) { + testCases := []struct { + description string + givenImps []openrtb.Imp + expectedImps map[string][]openrtb.Imp + expectedError string + }{ + { + description: "Nil", + givenImps: nil, + expectedImps: map[string][]openrtb.Imp{}, + expectedError: "", + }, + { + description: "Empty", + givenImps: []openrtb.Imp{}, + expectedImps: map[string][]openrtb.Imp{}, + expectedError: "", + }, + { + description: "1 Imp, 1 Bidder", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "1 Imp, 2 Bidders", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"},"bidderB":{"imp1ParamB":"imp1ValueB"}}}}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamB":"imp1ValueB"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "2 Imps, 1 Bidders Each", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2ParamA":"imp2ValueA"}}}}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2ParamA":"imp2ValueA"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "2 Imps, 2 Bidders Each", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}}}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, + }, + }, + expectedError: "", + }, + { + // This is a "happy path" integration test. Functionality is covered in detail by TestCreateSanitizedImpExt. + description: "Other Fields - 2 Imps, 2 Bidders Each", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}},"skadn":"imp2SkAdN"}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"},"skadn":"imp2SkAdN"}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"},"skadn":"imp2SkAdN"}`)}, + }, + }, + expectedError: "", + }, + { + // This is a "happy path" integration test. Functionality is covered in detail by TestExtractBidderExts. + description: "Legacy imp.ext.BIDDER - 2 Imps, 2 Bidders Each", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}`)}, + }, + expectedImps: map[string][]openrtb.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "Malformed imp.ext", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`malformed`)}, + }, + expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed imp.ext.prebid", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid": malformed}`)}, + }, + expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + }, + { + description: "Malformed imp.ext.prebid.bidder", + givenImps: []openrtb.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": malformed}}`)}, + }, + expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + }, + } + + for _, test := range testCases { + imps, err := splitImps(test.givenImps) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expectedImps, imps, test.description+":imps") + } +} + +func TestCreateSanitizedImpExt(t *testing.T) { + testCases := []struct { + description string + givenImpExt map[string]json.RawMessage + givenImpExtPrebid map[string]json.RawMessage + expected map[string]json.RawMessage + expectedError string + }{ + { + description: "Nil", + givenImpExt: nil, + givenImpExtPrebid: nil, + expected: map[string]json.RawMessage{}, + expectedError: "", + }, + { + description: "Empty", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebid: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + expectedError: "", + }, + { + description: "imp.ext.prebid - Bidders Only", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + }, + expected: map[string]json.RawMessage{ + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext.prebid - Bidders + Other Values", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "someOther": json.RawMessage(`"value"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"someOther":"value"}`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{ + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext + imp.ext.prebid - Prebid Bidders Only", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + }, + expected: map[string]json.RawMessage{ + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext + imp.ext.prebid - Prebid Bidders + Other Values", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "someOther": json.RawMessage(`"value"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"someOther":"value"}`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "Marshal Error - imp.ext.prebid", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "malformed": json.RawMessage(`json`), // String value without quotes. + }, + expected: nil, + expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'j' looking for beginning of value", + }, + } + + for _, test := range testCases { + result, err := createSanitizedImpExt(test.givenImpExt, test.givenImpExtPrebid) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestExtractBidderExts(t *testing.T) { + bidderAJSON := json.RawMessage(`{"paramA":"valueA"}}`) + bidderBJSON := json.RawMessage(`{"paramB":"valueB"}}`) + + testCases := []struct { + description string + givenImpExt map[string]json.RawMessage + givenImpExtPrebidBidders map[string]json.RawMessage + expected map[string]json.RawMessage + }{ + { + description: "Nil", + givenImpExt: nil, + givenImpExtPrebidBidders: nil, + expected: map[string]json.RawMessage{}, + }, + { + description: "Empty", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + }, + { + description: "One - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + { + description: "Many - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Special Names Ignored - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + }, + { + description: "One - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + { + description: "Many - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Special Names Not Treated Differently - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + expected: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + }, + { + description: "Mixed - Both - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderB": bidderBJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Mixed - Overwrites - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": json.RawMessage(`{"shouldBe":"Ignored"}}`)}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + } + + for _, test := range testCases { + result := extractBidderExts(test.givenImpExt, test.givenImpExtPrebidBidders) + assert.Equal(t, test.expected, result, test.description) + } +} + func TestCleanOpenRTBRequests(t *testing.T) { testCases := []struct { req AuctionRequest diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 570701603fd..e03c234b257 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -35,7 +35,7 @@ const ( BidderReservedData BidderName = "data" // Reserved for first party data. BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name. BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. - BidderReservedSKAdN BidderName = "skadn" // Reserved for SKAdNetwork OpenRTB extension. + BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index eb4655117ca..cc06f3806cf 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -12,6 +12,9 @@ import ( // PrebidExtKey represents the prebid extension key used in requests const PrebidExtKey = "prebid" +// PrebidExtBidderKey represents the field name within request.imp.ext.prebid reserved for bidder params. +const PrebidExtBidderKey = "bidder" + // ExtDevice defines the contract for bidrequest.device.ext type ExtDevice struct { // Attribute: diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 124e050ac5c..b2bb25e7401 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,9 +5,12 @@ import ( "errors" ) -// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved -// for first party data support. -const FirstPartyDataContextExtKey string = "context" +// FirstPartyDataContextExtKey defines the field name within request.ext reserved for first party data. +const FirstPartyDataContextExtKey = "context" + +// SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork. +const SKAdNExtKey = "skadn" + const MaxDecimalFigures int = 15 // ExtRequest defines the contract for bidrequest.ext From e53fd87999409dde31c67a0260da21993daa992e Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 11 Mar 2021 14:08:03 -0500 Subject: [PATCH 343/603] No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext --- adapters/rhythmone/rhythmone.go | 6 +- exchange/bidder.go | 4 - exchange/bidder_test.go | 4 - exchange/exchange.go | 85 +++++++++--------- exchange/exchange_test.go | 26 +++--- .../bid-ext-prebid-collision.json | 90 +++++++++++++++++++ exchange/exchangetest/bid-ext.json | 87 ++++++++++++++++++ exchange/seatbid.go | 8 -- openrtb_ext/bid.go | 4 +- 9 files changed, 236 insertions(+), 78 deletions(-) create mode 100644 exchange/exchangetest/bid-ext-prebid-collision.json create mode 100644 exchange/exchangetest/bid-ext.json delete mode 100644 exchange/seatbid.go diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go index 7dd94a494c8..a507e778550 100644 --- a/adapters/rhythmone/rhythmone.go +++ b/adapters/rhythmone/rhythmone.go @@ -132,9 +132,9 @@ func (a *RhythmoneAdapter) preProcess(req *openrtb.BidRequest, errors []error) ( errors = append(errors, err) return nil, "", errors } - bidderExtCopy := openrtb_ext.ExtBid{ - Bidder: rhythmoneExtCopy, - } + bidderExtCopy := struct { + Bidder json.RawMessage `json:"bidder,omitempty"` + }{rhythmoneExtCopy} impExtCopy, err := json.Marshal(&bidderExtCopy) if err != nil { errors = append(errors, err) diff --git a/exchange/bidder.go b/exchange/bidder.go index f916e205ace..6df5fafb5f6 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -83,10 +83,6 @@ type pbsOrtbSeatBid struct { // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. httpCalls []*openrtb_ext.ExtHttpCall - // ext contains the extension for this seatbid. - // if len(bids) > 0, this will become response.seatbid[i].ext.{bidder} on the final OpenRTB response. - // if len(bids) == 0, this will be ignored because the OpenRTB spec doesn't allow a SeatBid with 0 Bids. - ext json.RawMessage } // adaptBidder converts an adapters.Bidder into an exchange.adaptedBidder. diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b829ddd787c..dbf8a022255 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -135,10 +135,6 @@ func TestSingleBidder(t *testing.T) { if len(seatBid.httpCalls) != test.httpCallsLen { t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) } - - if len(seatBid.ext) != 0 { - t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext)) - } } } diff --git a/exchange/exchange.go b/exchange/exchange.go index b4fa3a614f6..45081c16e71 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -788,25 +788,9 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb // Return an openrtb seatBid for a bidder // BuildBidResponse is responsible for ensuring nil bid seatbids are not included func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb.SeatBid { - seatBid := new(openrtb.SeatBid) - seatBid.Seat = adapter.String() - // Prebid cannot support roadblocking - seatBid.Group = 0 - - if len(adapterBid.ext) > 0 { - sbExt := ExtSeatBid{ - Bidder: adapterBid.ext, - } - - ext, err := json.Marshal(sbExt) - if err != nil { - extError := openrtb_ext.ExtBidderError{ - Code: errortypes.ReadCode(err), - Message: fmt.Sprintf("Error writing SeatBid.Ext: %s", err.Error()), - } - adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, extError) - } - seatBid.Ext = ext + seatBid := &openrtb.SeatBid{ + Seat: adapter.String(), + Group: 0, // Prebid cannot support roadblocking } var errList []error @@ -818,39 +802,54 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B return seatBid } -// Create the Bid array inside of SeatBid -func (e *exchange) makeBid(Bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb.Bid, []error) { - bids := make([]openrtb.Bid, 0, len(Bids)) - errList := make([]error, 0, 1) - for _, thisBid := range Bids { - bidExt := &openrtb_ext.ExtBid{ - Bidder: thisBid.bid.Ext, - Prebid: &openrtb_ext.ExtBidPrebid{ - Targeting: thisBid.bidTargets, - Type: thisBid.bidType, - Video: thisBid.bidVideo, - Events: thisBid.bidEvents, - DealPriority: thisBid.dealPriority, - DealTierSatisfied: thisBid.dealTierSatisfied, - }, +func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb.Bid, []error) { + result := make([]openrtb.Bid, 0, len(bids)) + errs := make([]error, 0, 1) + + for _, bid := range bids { + bidExtPrebid := &openrtb_ext.ExtBidPrebid{ + DealPriority: bid.dealPriority, + DealTierSatisfied: bid.dealTierSatisfied, + Events: bid.bidEvents, + Targeting: bid.bidTargets, + Type: bid.bidType, + Video: bid.bidVideo, } - if cacheInfo, found := e.getBidCacheInfo(thisBid, auc); found { - bidExt.Prebid.Cache = &openrtb_ext.ExtBidPrebidCache{ + + if cacheInfo, found := e.getBidCacheInfo(bid, auc); found { + bidExtPrebid.Cache = &openrtb_ext.ExtBidPrebidCache{ Bids: &cacheInfo, } } - ext, err := json.Marshal(bidExt) - if err != nil { - errList = append(errList, err) + + if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid); err != nil { + errs = append(errs, err) } else { - bids = append(bids, *thisBid.bid) - bids[len(bids)-1].Ext = ext + result = append(result, *bid.bid) + resultBid := &result[len(result)-1] + resultBid.Ext = bidExtJSON if !returnCreative { - bids[len(bids)-1].AdM = "" + resultBid.AdM = "" } } } - return bids, errList + return result, errs +} + +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid) (json.RawMessage, error) { + // no existing bid.ext. generate a bid.ext with just our prebid section populated. + if len(ext) == 0 { + bidExt := &openrtb_ext.ExtBid{Prebid: prebid} + return json.Marshal(bidExt) + } + + // update existing bid.ext with our prebid section. if bid.ext.prebid already exists, it will be overwritten. + var extMap map[string]interface{} + if err := json.Unmarshal(ext, &extMap); err != nil { + return nil, err + } + extMap[openrtb_ext.PrebidExtKey] = prebid + return json.Marshal(extMap) } // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7507ecef89d..f615d412c71 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1823,7 +1823,7 @@ func TestCategoryMapping(t *testing.T) { &bid1_4, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -1878,7 +1878,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { &bid1_4, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -1930,7 +1930,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { &bid1_3, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -2012,7 +2012,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { &bid1_3, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -2082,7 +2082,7 @@ func TestCategoryDedupe(t *testing.T) { &bid1_5, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -2162,7 +2162,7 @@ func TestNoCategoryDedupe(t *testing.T) { &bid1_5, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid @@ -2223,10 +2223,10 @@ func TestCategoryMappingBidderName(t *testing.T) { &bid1_2, } - seatBid1 := pbsOrtbSeatBid{innerBids1, "USD", nil, nil} + seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"} bidderName1 := openrtb_ext.BidderName("bidder1") - seatBid2 := pbsOrtbSeatBid{innerBids2, "USD", nil, nil} + seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"} bidderName2 := openrtb_ext.BidderName("bidder2") adapterBids[bidderName1] = &seatBid1 @@ -2277,10 +2277,10 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { &bid1_2, } - seatBid1 := pbsOrtbSeatBid{innerBids1, "USD", nil, nil} + seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"} bidderName1 := openrtb_ext.BidderName("bidder1") - seatBid2 := pbsOrtbSeatBid{innerBids2, "USD", nil, nil} + seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"} bidderName2 := openrtb_ext.BidderName("bidder2") adapterBids[bidderName1] = &seatBid1 @@ -2384,7 +2384,7 @@ func TestBidRejectionErrors(t *testing.T) { innerBids = append(innerBids, ¤tBid) } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} adapterBids[bidderName] = &seatBid @@ -2442,10 +2442,10 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { for i := 1; i < 10; i++ { adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) - seatBidApn1 := pbsOrtbSeatBid{innerBidsApn1, "USD", nil, nil} + seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} bidderNameApn1 := openrtb_ext.BidderName("appnexus1") - seatBidApn2 := pbsOrtbSeatBid{innerBidsApn2, "USD", nil, nil} + seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"} bidderNameApn2 := openrtb_ext.BidderName("appnexus2") adapterBids[bidderNameApn1] = &seatBidApn1 diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json new file mode 100644 index 00000000000..054671ce8d2 --- /dev/null +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -0,0 +1,90 @@ +{ + "description": "Verifies bid.ext values are left alone from the adapter, except for overwriting bid.ext.prebid if the adapter ext includes a collision.", + + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "prebid": { + "willBeOverwritten": "by core logic" + } + } + }, + "bidType": "video" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "prebid": { + "type": "video" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json new file mode 100644 index 00000000000..8211ac88eac --- /dev/null +++ b/exchange/exchangetest/bid-ext.json @@ -0,0 +1,87 @@ +{ + "description": "Verifies bid.ext values are left alone from the adapter, except for adding in bid.ext.prebid.", + + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue" + } + }, + "bidType": "video" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "prebid": { + "type": "video" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/seatbid.go b/exchange/seatbid.go deleted file mode 100644 index b675127410e..00000000000 --- a/exchange/seatbid.go +++ /dev/null @@ -1,8 +0,0 @@ -package exchange - -import "encoding/json" - -// ExtSeatBid defines the contract for bidresponse.seatbid.ext -type ExtSeatBid struct { - Bidder json.RawMessage `json:"bidder,omitempty"` -} diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 8f4d7a094bb..7d787cf83a6 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -1,14 +1,12 @@ package openrtb_ext import ( - "encoding/json" "fmt" ) // ExtBid defines the contract for bidresponse.seatbid.bid[i].ext type ExtBid struct { - Prebid *ExtBidPrebid `json:"prebid,omitempty"` - Bidder json.RawMessage `json:"bidder,omitempty"` + Prebid *ExtBidPrebid `json:"prebid,omitempty"` } // ExtBidPrebid defines the contract for bidresponse.seatbid.bid[i].ext.prebid From 549d4b20288c07fe3b36bcea5e2e7174a3cf82a3 Mon Sep 17 00:00:00 2001 From: agilfix Date: Tue, 16 Mar 2021 13:13:51 -0400 Subject: [PATCH 344/603] Typo fix: adyoulike bidder param debug description (#1755) --- static/bidder-params/adyoulike.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/bidder-params/adyoulike.json b/static/bidder-params/adyoulike.json index f426a0923d7..448de344739 100644 --- a/static/bidder-params/adyoulike.json +++ b/static/bidder-params/adyoulike.json @@ -26,8 +26,8 @@ }, "debug": { "type": "string", - "description": "Abitrary id used for debug purpose" + "description": "Arbitrary id used for debug purpose" } }, "required": ["placement"] -} \ No newline at end of file +} From 5b1a9195d1db4635250b071546df30c636abbd8d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 17 Mar 2021 08:57:41 -0400 Subject: [PATCH 345/603] Aliases: Better Error Message For Disabled Bidder (#1751) --- endpoints/openrtb2/auction.go | 13 +++++++++---- endpoints/openrtb2/auction_test.go | 8 ++++---- .../sample-requests/disabled/bad/bad-alias.json | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index e4567bbcd29..35230315763 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -915,12 +915,17 @@ func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequ } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { - for thisAlias, coreBidder := range aliases { + for alias, coreBidder := range aliases { + if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder) + } + if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", thisAlias, coreBidder) + return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) } - if thisAlias == coreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", thisAlias) + + if alias == coreBidder { + return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) } } return nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 768e60593a1..892b0579ec4 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -48,7 +48,7 @@ type testConfigValues struct { AliasJSON string `json:"aliases"` BlacklistedAccounts []string `json:"blacklistedAccts"` BlacklistedApps []string `json:"blacklistedApps"` - AdapterList []string `json:"disabledAdapters"` + DisabledAdapters []string `json:"disabledAdapters"` } func TestJsonSampleRequests(t *testing.T) { @@ -229,9 +229,9 @@ func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter { var adaptersConfig map[string]config.Adapter - if len(tc.AdapterList) > 0 { - adaptersConfig = make(map[string]config.Adapter, len(tc.AdapterList)) - for _, adapterName := range tc.AdapterList { + if len(tc.DisabledAdapters) > 0 { + adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters)) + for _, adapterName := range tc.DisabledAdapters { adaptersConfig[adapterName] = config.Adapter{Disabled: true} } } diff --git a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json index f4379dc09a2..862393081e2 100644 --- a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json +++ b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json @@ -1,7 +1,7 @@ { "description": "Request comes with an alias to a disabled bidder, we should throw error", "config": { - "disabledAdapters": ["appnexus", "rubicon"] + "disabledAdapters": ["appnexus"] }, "mockBidRequest": { "id": "some-request-id", @@ -32,5 +32,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to unknown bidder: appnexus\n" + "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to disabled bidder: appnexus\n" } From fc87d1dbcf274889beca5140dcac427a974c1046 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Wed, 17 Mar 2021 18:17:51 -0400 Subject: [PATCH 346/603] beachfront: Changes to support real 204 (#1737) --- adapters/adapterstest/test_json.go | 19 +- adapters/beachfront/beachfront.go | 20 +- .../beachfronttest/exemplary/adm-video.json | 1 + .../beachfronttest/exemplary/banner.json | 1 + .../supplemental/banner-204-with-body.json | 79 ++++++++ ...r-empty_array-200.json => banner-204.json} | 12 +- .../banner-and-adm-video-by-explicit.json | 1 + ...video-expected-204-response-on-banner.json | 171 ++++++++++++++++++ .../supplemental/banner-and-adm-video.json | 1 + .../supplemental/banner-and-nurl-video.json | 1 + .../supplemental/banner-bad-request-400.json | 1 + ...idder_response_unmarshal_error_banner.json | 1 + .../supplemental/bidfloor-below-min.json | 1 + .../supplemental/six-nine-combo.json | 1 + .../supplemental/two-four-combo.json | 1 + 15 files changed, 290 insertions(+), 21 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json rename adapters/beachfront/beachfronttest/supplemental/{banner-empty_array-200.json => banner-204.json} (81%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index a25a4f1905a..1e3178ed23d 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -123,7 +123,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) for i := 0; i < len(spec.BidResponses); i++ { - diffBidLists(t, filename, bidResponses[i].Bids, spec.BidResponses[i].Bids) + diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids) } } @@ -227,9 +227,24 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [ } } -func diffBidLists(t *testing.T, filename string, actual []*adapters.TypedBid, expected []expectedBid) { +func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) { t.Helper() + if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) { + if len(expected) == 0 { + t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename) + } + + t.Fatalf("%s: mockResponses included unexpected nil or empty response", filename) + } + + // Expected nil response - give diffBids something to work with. + if response == nil { + response = new(adapters.BidderResponse) + } + + actual := response.Bids + if len(actual) != len(expected) { t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual)) } diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 3a431c9cf3a..58554577c87 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -76,6 +76,7 @@ type beachfrontBannerRequest struct { AdapterVersion string `json:"adapterVersion"` IP string `json:"ip"` RequestID string `json:"requestId"` + Real204 bool `json:"real204"` } type beachfrontSlot struct { @@ -367,6 +368,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e if request.Imp[0].Secure != nil { bfr.Secure = *request.Imp[0].Secure } + bfr.Real204 = true return bfr, errs } @@ -465,7 +467,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } bfReqs[i].Request.Imp = nil - bfReqs[i].Request.Imp = make([]openrtb.Imp, 1, 1) + bfReqs[i].Request.Imp = make([]openrtb.Imp, 1) bfReqs[i].Request.Imp[0] = imp } @@ -481,18 +483,12 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var bids []openrtb.Bid - - // The case of response status == 200 and response body length == 2 below covers the case of the banner endpoint returning - // an empty JSON array ('[]'), which is functionally no content. - if response.StatusCode == http.StatusNoContent || (response.StatusCode == http.StatusOK && len(response.Body) <= 2) { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("no content or truncated content received from server. status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), - }} + if response.StatusCode == http.StatusNoContent { + return nil, nil } if response.StatusCode >= http.StatusInternalServerError { - return nil, []error{&errortypes.BadInput{ + return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("server error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), }} } @@ -507,8 +503,9 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return nil, []error{fmt.Errorf("unexpected status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri)} } - var xtrnal openrtb.BidRequest + var bids []openrtb.Bid var errs = make([]error, 0) + var xtrnal openrtb.BidRequest // For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will // only get the User struct and everything else will be nil @@ -524,6 +521,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern var dur beachfrontVideoBidExtension bidResponse := adapters.NewBidderResponseWithBidsCapacity(BidCapacity) + for i := 0; i < len(bids); i++ { // If we unmarshal without an error, this is an AdM video diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json index 26b050f263d..edde1301dcc 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video.json +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json @@ -96,6 +96,7 @@ "expectedBidResponses": [ { + "currency": "USD", "bids": [ { "bid": { diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json index e3dcffa3d08..1a8e830521d 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/banner.json @@ -45,6 +45,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json new file mode 100644 index 00000000000..edba490e814 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "id": "dudImp", + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "dudAppId1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "dudImp", + "id": "dudAppId1", + "bidfloor": 0.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": { + } + } + }, + "mockResponse": { + "status": 204, + "body": [ + { + "something": "where nothing should be" + } + ] + } + } + ], + + "expectedBidResponses": [] +} + diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/banner-204.json similarity index 81% rename from adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json rename to adapters/beachfront/beachfronttest/supplemental/banner-204.json index 8e607f67663..69aa0b882a9 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204.json @@ -46,6 +46,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -63,18 +64,13 @@ } }, "mockResponse": { - "status": 200, - "body": [] + "status": 204, + "body": "" } } ], - "expectedBidResponses": [], + "expectedBidResponses": [ - "expectedMakeBidsErrors": [ - { - "value": "no content or truncated content received from server. status code 200 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info", - "comparison": "literal" - } ] } diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json index c326a33a642..b060efe6f03 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json @@ -60,6 +60,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json new file mode 100644 index 00000000000..240994bb370 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json @@ -0,0 +1,171 @@ +{ + "mockBidRequest": { + "id": "banner-and-video", + "imp": [ + { + "id": "mix1", + "ext": { + "bidder": { + "bidfloor": 0.41, + "appIds": { + "banner": "bannerAppId1", + "video": "videoAppId1" + } + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "mix1", + "id": "bannerAppId1", + "bidfloor": 0.41, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "deviceOs": "", + "deviceModel": "", + "isMobile": 0, + "ua": "", + "ip": "255.255.255.255", + "dnt": 0, + "user": {}, + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "requestId": "banner-and-video" + } + }, + "mockResponse": { + "status": 204, + "body": "" + } + }, + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "banner-and-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 0.41, + "secure": 1, + "id": "mix1" + } + ], + "site": { + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html" + }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "61b87329-8790-47b7-90dd-c53ae7ce1723", + "seatbid": [ + { + "bid": [ + { + "id": "5d839458f73decdc1572b7f6", + "impid": "mix1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250 + } + ], + "seat": "bfb-io-s1" + } + ], + "bidid": "5d839458f73decdc1572b7f6", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + {"bids":[]}, + { + "bids": [ + { + "bid": { + "id": "mix1AdmVideo", + "impid": "mix1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json index f67903d0866..18e8ca7a1bf 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json @@ -59,6 +59,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json index b950d73400b..2ef8ca585e1 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json @@ -57,6 +57,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json index 1771f393361..35926b3c943 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json @@ -45,6 +45,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json index b751f763c0d..e7869d36d6d 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json @@ -44,6 +44,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json index f5d4e8228c2..7ebe6cf4aa0 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json @@ -44,6 +44,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, diff --git a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json index 4c009ff08da..9a8ca7343e4 100644 --- a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json +++ b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json @@ -189,6 +189,7 @@ ], "domain": "example.com", "page": "http://example.com/whatever/something.html", + "real204": true, "referrer": "", "search": "", "secure": 0, diff --git a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json index d48962e6641..ac1ca19f6d3 100644 --- a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json +++ b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json @@ -97,6 +97,7 @@ ], "domain":"example.com", "page":"http://example.com/whatever/something.html", + "real204": true, "referrer":"", "search":"", "secure":0, From bdf1e7b3e13bdf87d3282bf74472fc66504537d5 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 18 Mar 2021 12:16:14 -0400 Subject: [PATCH 347/603] Fix race condition in 33across.go (#1757) Co-authored-by: Gus Carreon --- adapters/33across/33across.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index b9655faab7f..a48c18e9f58 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -228,7 +228,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { - videoCopy := video + videoCopy := *video if videoCopy.W == 0 || videoCopy.H == 0 || videoCopy.Protocols == nil || @@ -252,7 +252,7 @@ func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, err } } - return videoCopy, nil + return &videoCopy, nil } func getBidType(ext bidExt) openrtb_ext.BidType { From f7df258f061788ef7e72529115aa5fd554fa9f16 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 18 Mar 2021 12:31:01 -0400 Subject: [PATCH 348/603] Revert "Fix race condition in 33across.go (#1757)" (#1763) This reverts commit bdf1e7b3e13bdf87d3282bf74472fc66504537d5. --- adapters/33across/33across.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index a48c18e9f58..b9655faab7f 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -228,7 +228,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { - videoCopy := *video + videoCopy := video if videoCopy.W == 0 || videoCopy.H == 0 || videoCopy.Protocols == nil || @@ -252,7 +252,7 @@ func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, err } } - return &videoCopy, nil + return videoCopy, nil } func getBidType(ext bidExt) openrtb_ext.BidType { From eb6c0e65902c2c3c074dd03c6cbe5f70f37a7c74 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 18 Mar 2021 14:11:42 -0400 Subject: [PATCH 349/603] Replace TravisCI With GitHub Actions (#1754) * Initial Commit * Finished Configuration * Remove TravisCI * Remove TravisCI * Fix Go Version Badge * Correct Fix For Go Version Badge * Removed Custom Config File Name --- .github/release-drafter.yml | 9 +++++++++ .github/workflows/release.yml | 26 ++++++++++++++++++++++++++ .travis.yml | 30 ------------------------------ README.md | 3 ++- docs/developers/automated-tests.md | 2 +- docs/developers/contributing.md | 2 +- main.go | 2 +- 7 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..720e2f043f0 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,9 @@ +change-template: '* $TITLE (#$NUMBER)' +template: | + ## Changes + + $CHANGES + + ## Contributors + + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..fb9b6592308 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + release: + name: Create Release + if: github.event.base_ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Get Version + id: get_version + run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} + + - name: Create & Publish Release + uses: release-drafter/release-drafter@v5.12.1 + with: + name: ${{ steps.get_version.outputs.version }} + tag: ${{ steps.get_version.outputs.version }} + version: ${{ steps.get_version.outputs.version }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 97d8cea4d0d..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: go - -go: - - '1.14.2' - - '1.15' - -go_import_path: github.com/prebid/prebid-server - -env: -- GO111MODULE=on - -script: - - "./validate.sh --nofmt --cov --race 10" - -before_deploy: - - go get github.com/mitchellh/gox - - gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...; - -deploy: - provider: releases - skip_cleanup: true - api_key: - secure: TSJcbIpg2zTJuzUXwv0Un5DPztDTeIKQ2BuuO9KiWYY3Td/nKn0flTYE6B5O6iVqE96HKyj2j0W51rhnRTNDReRZv76L+YXLTJOTQEEQY/A+7XaUXRT0KIbr1EHaeU+4uPJe/8YXxq+nFNeqOjj+LY457WbvnQTIbraAmCgi4yNq4JR+J9BoCELkX0SnU7oq+brq9tJNL3V+7EHIVH6ZLa1lWOrapMnbrVils8gwzWR8XpbdaI+Sn30AGOFKZ0WE2ojZkZb8oZxyX0HKarIiykfZUUzRhlXlTJ0D81QOdc5AtPNR/2dqUXsUE8mRav9R3AJM2BCS2pnP29orCRQU/kxS/mRfx2oZhkr+OHPsNbJcGNSbqNKlM13bX2nL1ZJsJ6xL0VrkBFYlI01SWR12CT9DhZSqTmGPNEkt3fdzwuYtkJNfthb9e9obscnmJEHPSiZRv9dv/stP5LVJJHfFdrzM4+Qo3MCxLNOhmc+p93gsZPeuDGDlx8Tqv1KpN7sp0glbmOwyFAwbCVh5can/JPIAKsQi9VRyZAJvn+7sqqZCExN4TvFArq7pe0LjIVHUQZP9g/vS8HobQnPutmGxf8HqzVVEBnjMsXuiY4cVRecXVRM7crfJjLGr2e9ywIkUZMSD+bRkbRUZ0QQQPvWtcgRw5JmLKG9jDklj8BDkON8= - file: - - prebid-server_linux_386 - on: - repo: prebid/prebid-server - tags: true - branch: - - master diff --git a/README.md b/README.md index 32bf4575c94..a619533ea74 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -[![Build Status](https://travis-ci.org/prebid/prebid-server.svg?branch=master)](https://travis-ci.org/prebid/prebid-server) +[![Build](https://img.shields.io/github/workflow/status/prebid/prebid-server/Validate/master?style=flat-square)](https://github.com/prebid/prebid-server/actions/workflows/validate.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/prebid/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/prebid/prebid-server) +![Go Version](https://img.shields.io/github/go-mod/go-version/prebid/prebid-server?style=flat-square) # Prebid Server diff --git a/docs/developers/automated-tests.md b/docs/developers/automated-tests.md index 0dff9b04212..6814fba385c 100644 --- a/docs/developers/automated-tests.md +++ b/docs/developers/automated-tests.md @@ -1,6 +1,6 @@ # Automated Tests -This project uses [TravisCI](https://travis-ci.org/) to make sure that every PR passes automated tests. +This project uses GitHub Actions to make sure that every PR passes automated tests. To reproduce these tests locally, use: ``` diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md index 2dafa67fb2e..2a6a574ed14 100644 --- a/docs/developers/contributing.md +++ b/docs/developers/contributing.md @@ -40,7 +40,7 @@ those updates must be submitted in the same Pull Request as the code changes. When you're ready, [submit a Pull Request](https://help.github.com/articles/creating-a-pull-request/) against the `master` branch of [our GitHub repository](https://github.com/prebid/prebid-server/compare). -Pull Requests will be vetted through [Travis CI](https://travis-ci.com/). +Pull Requests will be vetted through GitHub Actions. To reproduce these same tests locally, do: ```bash diff --git a/main.go b/main.go index 2ae75a2e8cb..ebeabf29df8 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( // Rev holds binary revision string // Set manually at build time using: // go build -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -// Populated automatically at build / release time via .travis.yml +// Populated automatically at build / releases // `gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;` // See issue #559 var Rev string From a04d5e1824bc6c3766f12939945313d4c6746635 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 18 Mar 2021 11:44:01 -0700 Subject: [PATCH 350/603] Debug warnings (#1724) Co-authored-by: Veronika Solovei --- endpoints/openrtb2/amp_auction.go | 20 +- endpoints/openrtb2/amp_auction_test.go | 202 ++++++++++-------- endpoints/openrtb2/auction.go | 8 +- endpoints/openrtb2/auction_test.go | 53 ++++- .../supplementary/us-privacy-invalid.json | 52 +++++ errortypes/code.go | 2 + errortypes/errortypes.go | 22 +- exchange/bidder.go | 14 +- exchange/bidder_test.go | 7 +- exchange/exchange.go | 62 ++++-- exchange/exchange_test.go | 130 +++++++---- exchange/utils_test.go | 5 +- openrtb_ext/response.go | 7 +- privacy/ccpa/parsedpolicy.go | 5 +- 14 files changed, 410 insertions(+), 179 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8cc8e9bd454..1f3d622b80d 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -35,10 +35,10 @@ import ( const defaultAmpRequestTimeoutMillis = 900 type AmpResponse struct { - Targeting map[string]string `json:"targeting"` - Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` - Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"errors,omitempty"` - Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"warnings,omitempty"` + Targeting map[string]string `json:"targeting"` + Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` + Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"errors,omitempty"` + Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"warnings,omitempty"` } // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing @@ -239,9 +239,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } - warnings := make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError) + warnings := extResponse.Warnings + if warnings == nil { + warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage) + } for _, v := range errortypes.WarningOnly(errL) { - bidderErr := openrtb_ext.ExtBidderError{ + bidderErr := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(v), Message: v.Error(), } @@ -528,8 +531,9 @@ func readPolicy(consent string) (privacy.PolicyWriter, error) { return ccpa.ConsentWriter{consent}, nil } - return privacy.NilPolicyWriter{}, &errortypes.InvalidPrivacyConsent{ - Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + return privacy.NilPolicyWriter{}, &errortypes.Warning{ + Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, } } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 08461d40da3..77e76613c29 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -395,103 +395,119 @@ func TestCCPAConsent(t *testing.T) { } } -func TestNoConsent(t *testing.T) { - // Build Request - bid, err := getTestBidRequest(true, nil, true, nil) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) +func TestConsentWarnings(t *testing.T) { + type inputTest struct { + regs *openrtb_ext.ExtRegs + invalidConsentURL bool + expectedWarnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage } + invalidConsent := "invalid" - // Simulated Stored Request Backend - stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + bidderWarning := openrtb_ext.ExtBidderMessage{ + Code: 10003, + Message: "debug turned off for bidder", + } + invalidCCPAWarning := openrtb_ext.ExtBidderMessage{ + Code: 10001, + Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", + } + invalidConsentWarning := openrtb_ext.ExtBidderMessage{ + Code: 10001, + Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", + } - // Build Exchange Endpoint - mockExchange := &mockAmpExchange{} - endpoint, _ := NewAmpEndpoint( - mockExchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - ) + testData := []inputTest{ + { + regs: nil, + invalidConsentURL: false, + expectedWarnings: nil, + }, + { + regs: nil, + invalidConsentURL: true, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}}, + }, + { + regs: &openrtb_ext.ExtRegs{USPrivacy: "invalid"}, + invalidConsentURL: true, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning, invalidConsentWarning}, + openrtb_ext.BidderName("appnexus"): {bidderWarning}, + }, + }, + { + regs: &openrtb_ext.ExtRegs{USPrivacy: "1NYN"}, + invalidConsentURL: false, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}}, + }, + } - // Invoke Endpoint - request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) - responseRecorder := httptest.NewRecorder() - endpoint(responseRecorder, request, nil) + for _, testCase := range testData { - // Parse Response - var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { - t.Fatalf("Error unmarshalling response: %s", err.Error()) - } + bid, err := getTestBidRequest(true, nil, testCase.regs == nil, testCase.regs) + if err != nil { + t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + } - // Assert Result - result := mockExchange.lastRequest - assert.NotNil(t, result, "lastRequest") - assert.Nil(t, result.User, "lastRequest.User") - assert.Nil(t, result.Regs, "lastRequest.Regs") - assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) - assert.Empty(t, response.Warnings) -} + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} -func TestInvalidConsent(t *testing.T) { - // Build Request - bid, err := getTestBidRequest(true, nil, true, nil) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } + // Build Exchange Endpoint + var mockExchange exchange.Exchange + if testCase.regs != nil { + mockExchange = &mockAmpExchangeWarnings{} + } else { + mockExchange = &mockAmpExchange{} + } + endpoint, _ := NewAmpEndpoint( + mockExchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BuildBidderMap(), + ) - // Simulated Stored Request Backend - stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + // Invoke Endpoint + var request *http.Request - // Build Exchange Endpoint - mockExchange := &mockAmpExchange{} - endpoint, _ := NewAmpEndpoint( - mockExchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - ) + if testCase.invalidConsentURL { + request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) - // Invoke Endpoint - invalidConsent := "invalid" - request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) - responseRecorder := httptest.NewRecorder() - endpoint(responseRecorder, request, nil) + } else { + request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) + } - // Parse Response - var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { - t.Fatalf("Error unmarshalling response: %s", err.Error()) - } + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) - // Assert Result - expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ - openrtb_ext.BidderReservedGeneral: { - { - Code: 10001, - Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", - }, - }, + // Parse Response + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } + + // Assert Result + if testCase.regs == nil { + result := mockExchange.(*mockAmpExchange).lastRequest + assert.NotNil(t, result, "lastRequest") + assert.Nil(t, result.User, "lastRequest.User") + assert.Nil(t, result.Regs, "lastRequest.Regs") + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + if testCase.invalidConsentURL { + assert.Equal(t, testCase.expectedWarnings, response.Warnings) + } else { + assert.Empty(t, response.Warnings) + } + + } else { + assert.Equal(t, testCase.expectedWarnings, response.Warnings) + } } - result := mockExchange.lastRequest - assert.NotNil(t, result, "lastRequest") - assert.Nil(t, result.User, "lastRequest.User") - assert.Nil(t, result.Regs, "lastRequest.Regs") - assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) - assert.Equal(t, expectedWarnings, response.Warnings) } func TestNewAndLegacyConsentBothProvided(t *testing.T) { @@ -929,7 +945,7 @@ type mockAmpExchange struct { lastRequest *openrtb.BidRequest } -var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ +var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderName("openx"): { { Code: 1, @@ -962,6 +978,21 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq return response, nil } +type mockAmpExchangeWarnings struct{} + +func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { + response := &openrtb.BidResponse{ + SeatBid: []openrtb.SeatBid{{ + Bid: []openrtb.Bid{{ + AdM: "", + Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), + }}, + }}, + Ext: json.RawMessage(`{ "warnings": {"appnexus": [{"code": 10003, "message": "debug turned off for bidder"}] }}`), + } + return response, nil +} + func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { var width uint64 = 300 var height uint64 = 300 @@ -1025,7 +1056,6 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, Ext: regsExtData, } } - return json.Marshal(bidRequest) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 35230315763..d26623da348 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -139,6 +139,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + warnings := errortypes.WarningOnly(errL) ctx := context.Background() @@ -179,6 +180,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http RequestType: labels.RType, StartTime: start, LegacyLabels: labels, + Warnings: warnings, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) @@ -363,8 +365,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { - if _, invalidConsent := err.(*errortypes.InvalidPrivacyConsent); invalidConsent { - errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) + if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) consentWriter := ccpa.ConsentWriter{Consent: ""} if err := consentWriter.Write(req); err != nil { return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 892b0579ec4..19c58f05ae0 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1505,7 +1505,9 @@ func TestCCPAInvalid(t *testing.T) { errL := deps.validateRequest(&req) - expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"} + expectedWarning := errortypes.Warning{ + Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode} assert.ElementsMatch(t, errL, []error{&expectedWarning}) assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") @@ -2104,6 +2106,55 @@ func TestIOS14EndToEnd(t *testing.T) { assert.Equal(t, &lmtOne, result.Device.Lmt) } +func TestAuctionWarnings(t *testing.T) { + reqBody := validRequest(t, "us-privacy-invalid.json") + deps := &endpointDeps{ + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + if recorder.Code != http.StatusOK { + t.Errorf("Endpoint should return a 200") + } + warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings + if !assert.Len(t, warnings, 1, "One warning should be returned from exchange") { + t.FailNow() + } + actualWarning := warnings[0].(*errortypes.Warning) + expectedMessage := "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)" + assert.Equal(t, expectedMessage, actualWarning.Message, "Warning message is incorrect") + + assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") +} + +// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings. +type warningsCheckExchange struct { + auctionRequest exchange.AuctionRequest +} + +func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { + e.auctionRequest = r + return nil, nil +} + // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { gotRequest *openrtb.BidRequest diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json new file mode 100644 index 00000000000..2ccdfb7ccdc --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json @@ -0,0 +1,52 @@ +{ + "description": "Well formed amp request with invalid CCPA consent value", + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "source": { + "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" + }, + "tmax": 1000, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "regs": { + "ext": { + "us_privacy": "{invalid}" + } + }, + "user": { + "ext": {} + } + }, + "expectedBidResponse": { + "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "bidid":"test bid id", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/errortypes/code.go b/errortypes/code.go index 80a5eb45542..2749b978006 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -17,6 +17,8 @@ const ( const ( UnknownWarningCode = 10999 InvalidPrivacyConsentWarningCode = iota + 10000 + AccountLevelDebugDisabledWarningCode + BidderLevelDebugDisabledWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index c953f9b7e08..1fed2d7da6e 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -167,7 +167,8 @@ func (err *BidderTemporarilyDisabled) Severity() Severity { // Warning is a generic non-fatal error. type Warning struct { - Message string + Message string + WarningCode int } func (err *Warning) Error() string { @@ -175,26 +176,9 @@ func (err *Warning) Error() string { } func (err *Warning) Code() int { - return UnknownWarningCode + return err.WarningCode } func (err *Warning) Severity() Severity { return SeverityWarning } - -// InvalidPrivacyConsent is a warning for when the privacy consent string is invalid and is ignored. -type InvalidPrivacyConsent struct { - Message string -} - -func (err *InvalidPrivacyConsent) Error() string { - return err.Message -} - -func (err *InvalidPrivacyConsent) Code() int { - return InvalidPrivacyConsentWarningCode -} - -func (err *InvalidPrivacyConsent) Severity() Severity { - return SeverityWarning -} diff --git a/exchange/bidder.go b/exchange/bidder.go index 6df5fafb5f6..b0818dfdbe3 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -164,9 +164,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if accountDebugAllowed && bidder.config.DebugInfo.Allow { - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) + } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index dbf8a022255..e4eb7b9574e 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -20,6 +20,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/errortypes" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -109,9 +110,13 @@ func TestSingleBidder(t *testing.T) { } // Make sure the returned values are what we expect - if len(errs) != 0 { + if len(errortypes.FatalOnly(errs)) != 0 { t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) } + + if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 { + t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs)) + } if len(seatBid.bids) != len(mockBidderResponse.Bids) { t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) } diff --git a/exchange/exchange.go b/exchange/exchange.go index 45081c16e71..d8ac60ba46a 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -66,7 +66,8 @@ type exchange struct { // Container to pass out response ext data from the GetAllBids goroutines back into the main thread type seatResponseExtra struct { ResponseTimeMillis int - Errors []openrtb_ext.ExtBidderError + Errors []openrtb_ext.ExtBidderMessage + Warnings []openrtb_ext.ExtBidderMessage // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. HttpCalls []*openrtb_ext.ExtHttpCall @@ -106,6 +107,7 @@ type AuctionRequest struct { UserSyncs IdFetcher RequestType metrics.RequestType StartTime time.Time + Warnings []error // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -138,9 +140,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * debugLog = &DebugLog{Enabled: false} } - debugInfo := getDebugInfo(r.BidRequest, requestExt) + requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo = debugInfo && r.Account.DebugAllow + debugInfo := requestDebugInfo && r.Account.DebugAllow debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow if debugInfo { @@ -236,6 +238,22 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + if !r.Account.DebugAllow && requestDebugInfo { + accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ + Code: errortypes.AccountLevelDebugDisabledWarningCode, + Message: "debug turned off for account", + } + bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], accountDebugDisabledWarning) + } + + for _, warning := range r.Warnings { + generalWarning := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(warning), + Message: warning.Error(), + } + bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], generalWarning) + } + // Build the response return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } @@ -397,11 +415,11 @@ func (e *exchange) getAllBids( // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start)) - serr := errsToBidderErrors(err) bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids) bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err) // Append any bid validation errors to the error list - ae.Errors = serr + ae.Errors = errsToBidderErrors(err) + ae.Warnings = errsToBidderWarnings(err) brw.adapterExtra = ae if bids != nil { for _, bid := range bids.bids { @@ -494,13 +512,29 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { return ret } -func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { - serr := make([]openrtb_ext.ExtBidderError, len(errs)) - for i := 0; i < len(errs); i++ { - serr[i].Code = errortypes.ReadCode(errs[i]) - serr[i].Message = errs[i].Error() +func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderMessage { + sErr := make([]openrtb_ext.ExtBidderMessage, 0) + for _, err := range errortypes.FatalOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(err), + Message: err.Error(), + } + sErr = append(sErr, newErr) } - return serr + + return sErr +} + +func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { + sWarn := make([]openrtb_ext.ExtBidderMessage, 0) + for _, warn := range errortypes.WarningOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(warn), + Message: warn.Error(), + } + sWarn = append(sWarn, newErr) + } + return sWarn } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester @@ -749,7 +783,8 @@ func getPrimaryAdServer(adServerId int) (string, error) { func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse { req := r.BidRequest bidResponseExt := &openrtb_ext.ExtBidResponse{ - Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)), + Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), ResponseTimeMillis: make(map[openrtb_ext.BidderName]int, len(adapterBids)), RequestTimeoutMillis: req.TMax, } @@ -771,6 +806,9 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb if debugInfo && len(responseExtra.HttpCalls) > 0 { bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } + if len(responseExtra.Warnings) > 0 { + bidResponseExt.Warnings[bidderName] = responseExtra.Warnings + } // Only make an entry for bidder errors if the bidder reported any. if len(responseExtra.Errors) > 0 { bidResponseExt.Errors[bidderName] = responseExtra.Errors diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index f615d412c71..3eaa625f74f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/errortypes" "io/ioutil" "net/http" "net/http/httptest" @@ -141,7 +142,7 @@ func TestCharacterEscape(t *testing.T) { adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1) adapterExtra["appnexus"] = &seatResponseExtra{ ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, + Errors: []openrtb_ext.ExtBidderMessage{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, } var errList []error @@ -180,59 +181,68 @@ func TestDebugBehaviour(t *testing.T) { } type aTest struct { - desc string - in inTest - out outTest - debugData debugData + desc string + in inTest + out outTest + debugData debugData + generateWarnings bool } testCases := []aTest{ { - desc: "test flag equals zero, ext debug flag false, no debug info expected", - in: inTest{test: 0, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag false, no debug info expected", + in: inTest{test: 0, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals zero, ext debug flag true, debug info expected", - in: inTest{test: 0, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag true, debug info expected", + in: inTest{test: 0, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals 1, ext debug flag false, debug info expected", - in: inTest{test: 1, debug: false}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag false, debug info expected", + in: inTest{test: 1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals 1, ext debug flag true, debug info expected", - in: inTest{test: 1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag true, debug info expected", + in: inTest{test: 1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", - in: inTest{test: 2, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", + in: inTest{test: 2, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: true, }, { - desc: "test account level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + desc: "test account level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, false}, + generateWarnings: true, }, { - desc: "test bidder level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, + desc: "test bidder level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{false, true}, + generateWarnings: true, }, } @@ -243,9 +253,9 @@ func TestDebugBehaviour(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") - if error != nil { - t.Errorf("Failed to create a category Fetcher: %v", error) + categoriesFetcher, err := newCategoryFetcher("./test/category-mapping") + if err != nil { + t.Errorf("Failed to create a category Fetcher: %v", err) } bidRequest := &openrtb.BidRequest{ @@ -305,6 +315,13 @@ func TestDebugBehaviour(t *testing.T) { UserSyncs: &emptyUsersync{}, StartTime: time.Now(), } + if test.generateWarnings { + var errL []error + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent test warning."), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) + auctionRequest.Warnings = errL + } // Run test outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) @@ -339,6 +356,35 @@ func TestDebugBehaviour(t *testing.T) { } else { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } + + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") + } + + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if test.generateWarnings { + assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") + } else { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + } + assert.NotNil(t, actualExt.Warnings["appnexus"], "bidder warning should be present") + assert.Equal(t, "debug turned off for bidder", actualExt.Warnings["appnexus"][0].Message, "account debug disabled message should be present") + } + + if test.generateWarnings { + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + CCPAWarningPresent := false + for _, warn := range actualExt.Warnings["general"] { + if warn.Code == errortypes.InvalidPrivacyConsentWarningCode { + CCPAWarningPresent = true + break + } + } + assert.True(t, CCPAWarningPresent, "CCPA Warning should be present") + } + } } @@ -393,7 +439,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher - debugLog := DebugLog{} + debugLog := DebugLog{Enabled: true} for _, testCase := range testCases { bidRequest := &openrtb.BidRequest{ @@ -762,7 +808,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ bidderName: { ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{ + Errors: []openrtb_ext.ExtBidderMessage{ { Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"", diff --git a/exchange/utils_test.go b/exchange/utils_test.go index f089d70523b..1945aee1b98 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -637,7 +637,10 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { description: "Invalid Consent", reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), - expectError: &errortypes.InvalidPrivacyConsent{"request.regs.ext.us_privacy must contain 4 characters"}, + expectError: &errortypes.Warning{ + Message: "request.regs.ext.us_privacy must contain 4 characters", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, { description: "Invalid No Sale Bidders", diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 02370d19376..d517704f44d 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -8,7 +8,8 @@ import ( type ExtBidResponse struct { Debug *ExtResponseDebug `json:"debug,omitempty"` // Errors defines the contract for bidresponse.ext.errors - Errors map[BidderName][]ExtBidderError `json:"errors,omitempty"` + Errors map[BidderName][]ExtBidderMessage `json:"errors,omitempty"` + Warnings map[BidderName][]ExtBidderMessage `json:"warnings,omitempty"` // ResponseTimeMillis defines the contract for bidresponse.ext.responsetimemillis ResponseTimeMillis map[BidderName]int `json:"responsetimemillis,omitempty"` // RequestTimeoutMillis returns the timeout used in the auction. @@ -47,8 +48,8 @@ type ExtUserSync struct { Type UserSyncType `json:"type"` } -// ExtBidderError defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. -type ExtBidderError struct { +// ExtBidderMessage defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. +type ExtBidderMessage struct { Code int `json:"code"` Message string `json:"message"` } diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 3c934e67822..7b9c2d1fa7c 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -43,7 +43,10 @@ func (p Policy) Parse(validBidders map[string]struct{}) (ParsedPolicy, error) { consentOptOut, err := parseConsent(p.Consent) if err != nil { msg := fmt.Sprintf("request.regs.ext.us_privacy %s", err.Error()) - return ParsedPolicy{}, &errortypes.InvalidPrivacyConsent{Message: msg} + return ParsedPolicy{}, &errortypes.Warning{ + Message: msg, + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + } } noSaleForAllBidders, noSaleSpecificBidders, err := parseNoSaleBidders(p.NoSaleBidders, validBidders) From c675c188374754129c3bf133303b35507fcc3b4c Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Fri, 19 Mar 2021 23:47:51 +0200 Subject: [PATCH 351/603] Rubicon: Support sending segments to XAPI (#1752) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 46 ++++++++ .../rubicontest/exemplary/simple-video.json | 109 +++++++++++++++++- 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 91211a61d4e..32bf971cb6b 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -93,6 +93,10 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } +type rubiconUserDataExt struct { + TaxonomyName string `json:"taxonomyname"` +} + type rubiconUserExt struct { Consent string `json:"consent,omitempty"` DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` @@ -752,6 +756,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap userCopy := *request.User userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} + if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil { + errs = append(errs, err) + continue + } + if request.User.Ext != nil { var userExt *openrtb_ext.ExtUser if err = json.Unmarshal(userCopy.Ext, &userExt); err != nil { @@ -886,6 +895,43 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return requestData, errs } +func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb.Data) error { + var segmentIdsToCopy = make([]string, 0) + + for _, dataRecord := range data { + if dataRecord.Ext != nil { + var dataExtObject rubiconUserDataExt + err := json.Unmarshal(dataRecord.Ext, &dataExtObject) + if err != nil { + continue + } + if strings.EqualFold(dataExtObject.TaxonomyName, "iab") { + for _, segment := range dataRecord.Segment { + segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) + } + } + } + } + + userExtRPTarget := make(map[string]interface{}) + + if userExtRP.RP.Target != nil { + if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { + return &errortypes.BadInput{Message: err.Error()} + } + } + + userExtRPTarget["iab"] = segmentIdsToCopy + + if target, err := json.Marshal(&userExtRPTarget); err != nil { + return &errortypes.BadInput{Message: err.Error()} + } else { + userExtRP.RP.Target = target + } + + return nil +} + func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { rubiconUidsParam := mappedRubiconUidsParam{ tpIds: make([]rubiconExtUserTpID, 0), diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 76c64ff95ec..b85c28def44 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -9,6 +9,52 @@ "id": "1", "bundle": "com.wls.testwlsapplication" }, + "user": { + "data": [ + { + "ext": { + "taxonomyname": "iab" + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "taxonomyname": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "taxonomyname": "IaB" + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "taxonomyname": [ + "wrong iab type" + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + } + ] + }, "imp": [ { "id": "test-imp-id", @@ -30,8 +76,8 @@ "video": { }, "accountId": 1001, - "siteId":113932, - "zoneId":535510 + "siteId": 113932, + "zoneId": 535510 } } } @@ -52,6 +98,63 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, + "user": { + "data": [ + { + "ext": { + "taxonomyname": "iab" + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "taxonomyname": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "taxonomyname": "IaB" + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "taxonomyname": [ + "wrong iab type" + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + } + ], + "ext": { + "digitrust": null, + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, "app": { "id": "1", "ext": { @@ -91,7 +194,7 @@ }, "ext": { "rp": { - "track":{ + "track": { "mint": "", "mint_version": "" }, From ca53bd59e7399251518f692cbc7c00a987dc83d4 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 22 Mar 2021 07:51:40 -0400 Subject: [PATCH 352/603] validateNativeContextTypes function test cases (#1743) --- ...ext-type-content-incompatible-subtype.json | 25 +++++++++++ ...ext-type-product-incompatible-subtype.json | 27 ++++++++++++ ...text-type-social-incompatible-subtype.json | 27 ++++++++++++ .../contextsubtype-greater-than-max.json | 26 ++++++++++++ .../contextsubtype-invalid.json | 4 +- .../context-product-compatible-subtype.json | 41 +++++++++++++++++++ .../context-social-compatible-subtype.json | 41 +++++++++++++++++++ 7 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json create mode 100644 endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json new file mode 100644 index 00000000000..4297aa7596b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json @@ -0,0 +1,25 @@ +{ + "description": "Native bid request. Context type content (1) is incompatible with 'social' subcontext types (20~29). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json new file mode 100644 index 00000000000..3193833658b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json @@ -0,0 +1,27 @@ +{ + "description": "Native bid request. Context type product (3) is incompatible with 'social' subcontext types (10~19). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":3,\"contextsubtype\":11,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json new file mode 100644 index 00000000000..4ecbf4498fb --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json @@ -0,0 +1,27 @@ +{ + "description": "Native bid request. Context type social (2) is incompatible with 'product' subcontext types (30~39). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":2,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json new file mode 100644 index 00000000000..9b422380edf --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json @@ -0,0 +1,26 @@ +{ + "description": "Native bid request comes with a subcontext type greater than 500. Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":1,\"contextsubtype\":550,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json index 395e7034b0c..cdf4ef0c075 100644 --- a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json @@ -1,5 +1,5 @@ { - "description": "Bid request with invalid contextsubtype value inside the native.request field in its only imp element", + "description": "Native bid request comes with a contextsubtype greater than 32, 'ContextSubTypeProductReview'", "mockBidRequest": { "id": "req-id", "site": { @@ -10,7 +10,7 @@ { "id": "some-imp", "native": { - "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + "request": "{\"context\":1,\"contextsubtype\":41,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" }, "ext": { "appnexus": { diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json new file mode 100644 index 00000000000..dbf7b9c5e0d --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json @@ -0,0 +1,41 @@ +{ + "description": "Native bid request comes with a context type product (3) and compatible subcontext type", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":3,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id": "req-id", + "bidid": "test bid id", + "nbr": 0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json new file mode 100644 index 00000000000..41fb833d770 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json @@ -0,0 +1,41 @@ +{ + "description": "Native bid request comes with a context type social (2) and compatible subcontext type", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":2,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id": "req-id", + "bidid": "test bid id", + "nbr": 0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} From 7c8148e0367334eccef1855c9533697f37b27fb4 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 22 Mar 2021 13:08:17 -0400 Subject: [PATCH 353/603] Applogy: Fix Shared Memory Overwriting (#1758) --- adapters/applogy/applogy.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go index ebac02833ee..fe5fdc17933 100644 --- a/adapters/applogy/applogy.go +++ b/adapters/applogy/applogy.go @@ -25,7 +25,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E result := make([]*adapters.RequestData, 0, len(impressions)) errs := make([]error, 0, len(impressions)) - for i, impression := range impressions { + for _, impression := range impressions { if impression.Banner == nil && impression.Video == nil && impression.Native == nil { errs = append(errs, &errortypes.BadInput{ Message: "Applogy only supports banner, video or native ads", @@ -33,17 +33,17 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E continue } if impression.Banner != nil { - banner := impression.Banner - if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { - if len(banner.Format) == 0 { + if impression.Banner.W == nil || impression.Banner.H == nil || *impression.Banner.W == 0 || *impression.Banner.H == 0 { + if len(impression.Banner.Format) == 0 { errs = append(errs, &errortypes.BadInput{ Message: "banner size information missing", }) continue } - format := banner.Format[0] - banner.W = &format.W - banner.H = &format.H + banner := *impression.Banner + banner.W = openrtb.Uint64Ptr(banner.Format[0].W) + banner.H = openrtb.Uint64Ptr(banner.Format[0].H) + impression.Banner = &banner } } if len(impression.Ext) == 0 { @@ -70,7 +70,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E errs = append(errs, errors.New("Applogy token required")) continue } - request.Imp = impressions[i : i+1] + request.Imp = []openrtb.Imp{impression} body, err := json.Marshal(request) if err != nil { errs = append(errs, err) From 07aabefc800806c78cae3565e8e38bf6815df9fe Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 22 Mar 2021 13:09:24 -0400 Subject: [PATCH 354/603] Pubmatic: Fix Shared Memory Overwriting (#1759) --- adapters/pubmatic/pubmatic.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 2dfba673734..be3ebb313f3 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -418,8 +418,7 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { //In case of video, size could be derived from the player size if imp.Banner != nil { - imp.Banner.H = openrtb.Uint64Ptr(uint64(height)) - imp.Banner.W = openrtb.Uint64Ptr(uint64(width)) + imp.Banner = assignBannerHeightAndWidth(imp.Banner, uint64(height), uint64(width)) } } else { return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) @@ -428,25 +427,25 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { return nil } -func assignBannerSize(banner *openrtb.Banner) error { - if banner == nil { - return nil - } - +func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { if banner.W != nil && banner.H != nil { - return nil + return banner, nil } if len(banner.Format) == 0 { - return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) + return nil, errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(uint64) - *banner.W = banner.Format[0].W - banner.H = new(uint64) - *banner.H = banner.Format[0].H + return assignBannerHeightAndWidth(banner, banner.Format[0].H, banner.Format[0].H), nil +} - return nil +func assignBannerHeightAndWidth(banner *openrtb.Banner, h uint64, w uint64) *openrtb.Banner { + bannerCopy := *banner + + bannerCopy.W = openrtb.Uint64Ptr(w) + bannerCopy.H = openrtb.Uint64Ptr(h) + + return &bannerCopy } // parseImpressionObject parse the imp to get it ready to send to pubmatic @@ -489,9 +488,11 @@ func parseImpressionObject(imp *openrtb.Imp, wrapExt *string, pubID *string) err } if imp.Banner != nil { - if err := assignBannerSize(imp.Banner); err != nil { + bannerCopy, err := assignBannerSize(imp.Banner) + if err != nil { return err } + imp.Banner = bannerCopy } if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 { From 2f1a0e044d5bffc490383356e491bc54daf87ec9 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 22 Mar 2021 16:27:18 -0400 Subject: [PATCH 355/603] Beachfront: Fix Shared Memory Overwriting (#1762) * Fix race condition in Beachfront adapter * Removed nil check and simplified --- adapters/beachfront/beachfront.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 58554577c87..09dd9e93929 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -409,8 +409,11 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] bfReqs[i].Request = *request var secure int8 + var deviceCopy openrtb.Device if bfReqs[i].Request.Device == nil { - bfReqs[i].Request.Device = &openrtb.Device{} + deviceCopy = openrtb.Device{} + } else { + deviceCopy = *bfReqs[i].Request.Device } if beachfrontExt.VideoResponseType == "nurl" { @@ -418,13 +421,15 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } else { bfReqs[i].VideoResponseType = "adm" - if bfReqs[i].Request.Device.IP == "" { - bfReqs[i].Request.Device.IP = fakeIP + if deviceCopy.IP == "" { + deviceCopy.IP = fakeIP } } if bfReqs[i].Request.Site != nil && bfReqs[i].Request.Site.Domain == "" && bfReqs[i].Request.Site.Page != "" { - bfReqs[i].Request.Site.Domain = getDomain(bfReqs[i].Request.Site.Page) + siteCopy := *bfReqs[i].Request.Site + siteCopy.Domain = getDomain(bfReqs[i].Request.Site.Page) + bfReqs[i].Request.Site = &siteCopy secure = isSecure(bfReqs[i].Request.Site.Page) } @@ -439,10 +444,11 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } } - if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.DeviceType == 0 { + if deviceCopy.DeviceType == 0 { // More fine graned deviceType methods will be added in the future - bfReqs[i].Request.Device.DeviceType = fallBackDeviceType(request) + deviceCopy.DeviceType = fallBackDeviceType(request) } + bfReqs[i].Request.Device = &deviceCopy imp := request.Imp[i] From 080843b96b21a69b3e1dc45a7988e557dfe8ca06 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 22 Mar 2021 23:37:52 -0400 Subject: [PATCH 356/603] FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) --- endpoints/openrtb2/auction.go | 2 ++ ...=> valid-fpd-allowed-with-ext-bidder.json} | 33 +++++++++---------- ...valid-fpd-allowed-with-prebid-bidder.json} | 33 +++++++++---------- ...rstpartydata-imp-ext-multiple-bidders.json | 15 +++++++-- ...data-imp-ext-multiple-prebid-bidders.json | 15 +++++++-- .../firstpartydata-imp-ext-one-bidder.json | 10 ++++-- ...stpartydata-imp-ext-one-prebid-bidder.json | 10 ++++-- exchange/utils.go | 5 +++ exchange/utils_test.go | 11 +++++++ openrtb_ext/request.go | 5 ++- 10 files changed, 94 insertions(+), 45 deletions(-) rename endpoints/openrtb2/sample-requests/first-party-data/{valid-context-allowed-with-ext-bidder.json => valid-fpd-allowed-with-ext-bidder.json} (64%) rename endpoints/openrtb2/sample-requests/first-party-data/{valid-context-allowed-with-prebid-bidder.json => valid-fpd-allowed-with-prebid-bidder.json} (67%) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d26623da348..70d8e72629e 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -898,6 +898,8 @@ func isBidderToValidate(bidder string) bool { switch openrtb_ext.BidderName(bidder) { case openrtb_ext.BidderReservedContext: return false + case openrtb_ext.BidderReservedData: + return false case openrtb_ext.BidderReservedPrebid: return false case openrtb_ext.BidderReservedSKAdN: diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json similarity index 64% rename from endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json rename to endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json index 74dede0857f..c36ae0cd41d 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json @@ -21,30 +21,29 @@ "appnexus": { "placementId": 12883451 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } }] }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ], - "bidid":"test bid id", - "nbr":0 + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json similarity index 67% rename from endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json rename to endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json index 41461813c40..ad6298db39a 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json @@ -25,30 +25,29 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } }] }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ], - "bidid":"test bid id", - "nbr":0 + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json index 8004c3c2646..df0e4e33ea5 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json @@ -25,9 +25,12 @@ "siteId": 2, "zoneId": 3 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -57,9 +60,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -107,9 +113,12 @@ "siteId": 2, "zoneId": 3 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index 37654c454bc..ea4a980c63f 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -29,9 +29,12 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -61,9 +64,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -111,9 +117,12 @@ "siteId": 2, "zoneId": 3 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json index 6f0bab9529c..7e2c4b9a16d 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json @@ -20,9 +20,12 @@ "appnexus": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -52,9 +55,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index c931b0b9ee3..f1977426591 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -24,9 +24,12 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -56,9 +59,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/utils.go b/exchange/utils.go index 4ca02149453..0d9d0e3ced8 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -384,6 +384,10 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } } + if v, exists := impExt[openrtb_ext.FirstPartyDataExtKey]; exists { + sanitizedImpExt[openrtb_ext.FirstPartyDataExtKey] = v + } + if v, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { sanitizedImpExt[openrtb_ext.FirstPartyDataContextExtKey] = v } @@ -419,6 +423,7 @@ func extractBidderExts(impExt, impExtPrebidBidders map[string]json.RawMessage) m func isSpecialField(bidder string) bool { return bidder == openrtb_ext.FirstPartyDataContextExtKey || + bidder == openrtb_ext.FirstPartyDataExtKey || bidder == openrtb_ext.SKAdNExtKey || bidder == openrtb_ext.PrebidExtKey } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1945aee1b98..f8603f18f4f 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -233,6 +233,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { description: "imp.ext.prebid - Bidders Only", givenImpExt: map[string]json.RawMessage{ "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -240,6 +241,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { "bidder": json.RawMessage(`"anyBidder"`), }, expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -249,6 +251,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { description: "imp.ext.prebid - Bidders + Other Values", givenImpExt: map[string]json.RawMessage{ "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -258,6 +261,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { }, expected: map[string]json.RawMessage{ "prebid": json.RawMessage(`{"someOther":"value"}`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -267,11 +271,13 @@ func TestCreateSanitizedImpExt(t *testing.T) { description: "imp.ext", givenImpExt: map[string]json.RawMessage{ "anyBidder": json.RawMessage(`"anyBidderValues"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, givenImpExtPrebid: map[string]json.RawMessage{}, expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -282,6 +288,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { givenImpExt: map[string]json.RawMessage{ "anyBidder": json.RawMessage(`"anyBidderValues"`), "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -289,6 +296,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { "bidder": json.RawMessage(`"anyBidder"`), }, expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -299,6 +307,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { givenImpExt: map[string]json.RawMessage{ "anyBidder": json.RawMessage(`"anyBidderValues"`), "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -308,6 +317,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { }, expected: map[string]json.RawMessage{ "prebid": json.RawMessage(`{"someOther":"value"}`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, @@ -317,6 +327,7 @@ func TestCreateSanitizedImpExt(t *testing.T) { description: "Marshal Error - imp.ext.prebid", givenImpExt: map[string]json.RawMessage{ "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), "context": json.RawMessage(`"anyContext"`), "skadn": json.RawMessage(`"anySKAdNetwork"`), }, diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index b2bb25e7401..f673c737eab 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,7 +5,10 @@ import ( "errors" ) -// FirstPartyDataContextExtKey defines the field name within request.ext reserved for first party data. +// FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. +const FirstPartyDataExtKey = "data" + +// FirstPartyDataContextExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. const FirstPartyDataContextExtKey = "context" // SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork. From 8760bae5d8760409a5ab0d4c397acf92dfb89705 Mon Sep 17 00:00:00 2001 From: el-chuck Date: Tue, 23 Mar 2021 17:27:02 +0100 Subject: [PATCH 357/603] Smaato: Add support for app (#1767) Co-authored-by: Bernhard Pickenbrock --- adapters/smaato/smaato.go | 10 +- .../exemplary/simple-banner-app.json | 225 +++++++++++++++++ .../simple-banner-richMedia-app.json | 229 +++++++++++++++++ .../exemplary/simple-banner-richMedia.json | 2 +- .../smaatotest/exemplary/simple-banner.json | 2 +- .../smaatotest/exemplary/video-app.json | 230 ++++++++++++++++++ .../smaato/smaatotest/exemplary/video.json | 2 +- .../supplemental/bad-adm-response.json | 2 +- .../bad-imp-banner-format-req.json | 2 +- .../supplemental/no-consent-info.json | 2 +- .../supplemental/status-code-204.json | 139 +++++++++++ .../supplemental/status-code-400.json | 144 +++++++++++ 12 files changed, 982 insertions(+), 7 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner-app.json create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json create mode 100644 adapters/smaato/smaatotest/exemplary/video-app.json create mode 100644 adapters/smaato/smaatotest/supplemental/status-code-204.json create mode 100644 adapters/smaato/smaatotest/supplemental/status-code-400.json diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 1d5b29ab2c0..29b4a5848bc 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -14,7 +14,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const clientVersion = "prebid_server_0.1" +const clientVersion = "prebid_server_0.2" type adMarkupType string @@ -81,6 +81,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt i-- } } + if request.Site != nil { siteCopy := *request.Site siteCopy.Publisher = &openrtb.Publisher{ID: publisherID} @@ -98,6 +99,13 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt request.Site = &siteCopy } + if request.App != nil { + appCopy := *request.App + appCopy.Publisher = &openrtb.Publisher{ID: publisherID} + + request.App = &appCopy + } + if request.User != nil && request.User.Ext != nil { var userExt userExt var userExtRaw map[string]json.RawMessage diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json new file mode 100644 index 00000000000..8194f568c28 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json @@ -0,0 +1,225 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json new file mode 100644 index 00000000000..46722c4ff71 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json @@ -0,0 +1,229 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "gender": "M", + "keywords": "a,b", + "yob": 1984, + "ext": { + "consent": "gdprConsentString" + } + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
hello
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 7b662e8813a..1018dbc39ac 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -129,7 +129,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index a50fd9289e3..0ba4050a143 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json new file mode 100644 index 00000000000..bf939eb078a --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/video-app.json @@ -0,0 +1,230 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "postbid_iframe", + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + }, + "ext": { + "prebid": { + "auctiontimestamp": 1598262728811, + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "postbid_iframe", + "tagid": "130563103", + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index f2896d3d9d8..bad3825bb62 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -122,7 +122,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index 1fce58f0dfe..db724565d52 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json index b9560f0f9ca..768b4ef9d2c 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json +++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json @@ -40,7 +40,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } } diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info.json index 9e0ccfdcdde..b9a4294b00b 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/status-code-204.json b/adapters/smaato/smaatotest/supplemental/status-code-204.json new file mode 100644 index 00000000000..b409597f986 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/status-code-204.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-400.json b/adapters/smaato/smaatotest/supplemental/status-code-400.json new file mode 100644 index 00000000000..fc84c93e269 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/status-code-400.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file From bea52c642e6f4a314d36df383ddd60f3493170b1 Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 23 Mar 2021 18:45:23 +0200 Subject: [PATCH 358/603] Update sync types (#1770) --- adapters/adtarget/usersync.go | 2 +- adapters/adtarget/usersync_test.go | 2 +- adapters/adtelligent/usersync.go | 2 +- adapters/adtelligent/usersync_test.go | 2 +- adapters/mediafuse/usersync.go | 2 +- adapters/mediafuse/usersync_test.go | 2 +- config/config.go | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go index d720f110d89..088de8fb2ad 100644 --- a/adapters/adtarget/usersync.go +++ b/adapters/adtarget/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtarget", temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtarget", temp, adapters.SyncTypeIframe) } diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go index ea6146ceec8..e66b7b8377b 100644 --- a/adapters/adtarget/usersync_test.go +++ b/adapters/adtarget/usersync_test.go @@ -32,6 +32,6 @@ func TestAdtargetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adtelligent/usersync.go b/adapters/adtelligent/usersync.go index 613c8e294f0..9198b30fe6f 100644 --- a/adapters/adtelligent/usersync.go +++ b/adapters/adtelligent/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewAdtelligentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtelligent", temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtelligent", temp, adapters.SyncTypeIframe) } diff --git a/adapters/adtelligent/usersync_test.go b/adapters/adtelligent/usersync_test.go index 2430f377bd4..2ca0ddfc135 100644 --- a/adapters/adtelligent/usersync_test.go +++ b/adapters/adtelligent/usersync_test.go @@ -24,6 +24,6 @@ func TestAdtelligentSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mediafuse/usersync.go b/adapters/mediafuse/usersync.go index b91a6b5052c..d482ad774bd 100644 --- a/adapters/mediafuse/usersync.go +++ b/adapters/mediafuse/usersync.go @@ -8,5 +8,5 @@ import ( ) func NewMediafuseSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mediafuse", temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("mediafuse", temp, adapters.SyncTypeIframe) } diff --git a/adapters/mediafuse/usersync_test.go b/adapters/mediafuse/usersync_test.go index e3dfa06831d..95045689a87 100644 --- a/adapters/mediafuse/usersync_test.go +++ b/adapters/mediafuse/usersync_test.go @@ -24,6 +24,6 @@ func TestMediafuseSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.hbmp.mediafuse.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/config/config.go b/config/config.go index 22ce8faf0b6..bc15a947c7e 100644 --- a/config/config.go +++ b/config/config.go @@ -571,8 +571,8 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtarget, "https://sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + // openrtb_ext.BidderAdtarget doesn't have a good default. + // openrtb_ext.BidderAdtelligent doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderAdOcean doesn't have a good default. @@ -613,7 +613,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMediafuse, "https://sync.hbmp.mediafuse.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + // openrtb_ext.BidderMediafuse doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") From b9d28e7e39a7539ffb14b2a6299921d7c0d5c89d Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 24 Mar 2021 12:06:26 -0400 Subject: [PATCH 359/603] 33across: Fix Shared Memory Overwriting (#1764) This reverts commit f7df258f061788ef7e72529115aa5fd554fa9f16. --- adapters/33across/33across.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index b9655faab7f..a48c18e9f58 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -228,7 +228,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { - videoCopy := video + videoCopy := *video if videoCopy.W == 0 || videoCopy.H == 0 || videoCopy.Protocols == nil || @@ -252,7 +252,7 @@ func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, err } } - return videoCopy, nil + return &videoCopy, nil } func getBidType(ext bidExt) openrtb_ext.BidType { From ef32c8b092b88fc217e2b3dc79d4040a49b1823f Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 25 Mar 2021 12:59:26 -0400 Subject: [PATCH 360/603] Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon --- adapters/yeahmobi/yeahmobi.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index c62f5bc032f..d603397b09f 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -95,7 +95,9 @@ func transform(request *openrtb.BidRequest) { continue } - request.Imp[i].Native.Request = string(nativeReqByte) + nativeCopy := *request.Imp[i].Native + nativeCopy.Request = string(nativeReqByte) + request.Imp[i].Native = &nativeCopy } } } From bcfe9f2a92a8896182b689c77a6e2ad6b90cab0c Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 25 Mar 2021 13:16:48 -0400 Subject: [PATCH 361/603] Pubnative: Fix Shared Memory Overwriting (#1760) --- adapters/pubnative/pubnative.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 7d393f8ade9..9144d6ddaae 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -94,29 +94,35 @@ func convertImpression(imp *openrtb.Imp) error { } } if imp.Banner != nil { - err := convertBanner(imp.Banner) + bannerCopy, err := convertBanner(imp.Banner) if err != nil { return err } + imp.Banner = bannerCopy } return nil } // make sure that banner has openrtb 2.3-compatible size information -func convertBanner(banner *openrtb.Banner) error { +func convertBanner(banner *openrtb.Banner) (*openrtb.Banner, error) { if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { if len(banner.Format) > 0 { f := banner.Format[0] - banner.W = &f.W - banner.H = &f.H + + bannerCopy := *banner + + bannerCopy.W = openrtb.Uint64Ptr(f.W) + bannerCopy.H = openrtb.Uint64Ptr(f.H) + + return &bannerCopy, nil } else { - return &errortypes.BadInput{ + return nil, &errortypes.BadInput{ Message: "Size information missing for banner", } } } - return nil + return banner, nil } func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { From 6a3a8b8a44d0d3b6c34e93280d37f657af4803ad Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 25 Mar 2021 16:59:40 -0400 Subject: [PATCH 362/603] Add request for registration (#1780) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a619533ea74..83ac89a9807 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ For more information, see: - [Prebid Server Overview](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html) - [Current Bidders](http://prebid.org/dev-docs/pbs-bidders.html) +Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-server/hosting/pbs-hosting.html#optional-registration) to get on the mailing list for updates, etc. + ## Installation First install [Go](https://golang.org/doc/install) version 1.14 or newer. From 192f55ba67c211d31d363cea85c406f3c47a6f99 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 26 Mar 2021 13:19:25 -0400 Subject: [PATCH 363/603] Update OpenRTB Library (#1733) --- adapters/33across/33across.go | 20 +- adapters/acuityads/acuityads.go | 14 +- adapters/adapterstest/adapter_test_util.go | 10 +- adapters/adapterstest/test_json.go | 6 +- adapters/adform/adform.go | 31 +- adapters/adform/adform_test.go | 64 ++-- adapters/adgeneration/adgeneration.go | 24 +- adapters/adgeneration/adgeneration_test.go | 70 ++-- adapters/adhese/adhese.go | 30 +- adapters/adhese/utils.go | 16 +- adapters/adkernel/adkernel.go | 34 +- adapters/adkernelAdn/adkernelAdn.go | 34 +- adapters/adman/adman.go | 14 +- .../admantest/supplemental/bad_response.json | 2 +- adapters/admixer/admixer.go | 16 +- adapters/adocean/adocean.go | 28 +- adapters/adoppler/adoppler.go | 12 +- .../supplemental/invalid-response.json | 2 +- adapters/adot/adot.go | 13 +- adapters/adot/adot_test.go | 13 +- .../supplemental/unmarshal_error.json | 2 +- adapters/adpone/adpone.go | 8 +- .../adponetest/supplemental/bad_response.json | 2 +- adapters/adprime/adprime.go | 14 +- .../supplemental/bad_response.json | 2 +- adapters/adtarget/adtarget.go | 12 +- adapters/adtelligent/adtelligent.go | 12 +- adapters/advangelists/advangelists.go | 32 +- adapters/adyoulike/adyoulike.go | 12 +- .../supplemental/invalid-bid-response.json | 2 +- adapters/aja/aja.go | 12 +- adapters/amx/amx.go | 14 +- adapters/amx/amx_test.go | 48 +-- adapters/applogy/applogy.go | 14 +- adapters/appnexus/appnexus.go | 28 +- adapters/appnexus/appnexus_test.go | 132 ++++---- adapters/audienceNetwork/facebook.go | 121 +++---- adapters/avocet/avocet.go | 12 +- adapters/avocet/avocet_test.go | 22 +- adapters/beachfront/beachfront.go | 66 ++-- adapters/beintoo/beintoo.go | 18 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/between/between.go | 18 +- .../supplemental/bad-response-body.json | 2 +- adapters/bidder.go | 10 +- adapters/brightroll/brightroll.go | 16 +- adapters/colossus/colossus.go | 14 +- .../supplemental/bad_response.json | 2 +- adapters/connectad/connectad.go | 18 +- .../supplemental/badresponse.json | 2 +- adapters/consumable/adtypes.go | 7 +- adapters/consumable/consumable.go | 14 +- adapters/conversant/cnvr_legacy.go | 20 +- adapters/conversant/cnvr_legacy_test.go | 40 +-- adapters/conversant/conversant.go | 24 +- adapters/cpmstar/cpmstar.go | 14 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/datablocks/datablocks.go | 18 +- .../supplemental/bad-response-body.json | 2 +- adapters/decenterads/decenterads.go | 12 +- .../supplemental/bad_response.json | 2 +- adapters/deepintent/deepintent.go | 18 +- .../supplemental/bad_response.json | 2 +- adapters/dmx/dmx.go | 30 +- adapters/dmx/dmx_test.go | 253 ++++++++------- adapters/emx_digital/emx_digital.go | 26 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/engagebdr/engagebdr.go | 12 +- adapters/eplanning/eplanning.go | 22 +- adapters/epom/epom.go | 12 +- adapters/gamma/gamma.go | 34 +- adapters/gamoshi/gamoshi.go | 10 +- adapters/grid/grid.go | 14 +- .../gridtest/supplemental/bad_response.json | 2 +- adapters/gumgum/gumgum.go | 20 +- adapters/improvedigital/improvedigital.go | 10 +- .../supplemental/bad_response.json | 2 +- adapters/infoawarebidder.go | 12 +- adapters/infoawarebidder_test.go | 72 ++--- adapters/inmobi/inmobi.go | 12 +- adapters/invibes/invibes.go | 32 +- adapters/ix/ix.go | 37 ++- adapters/ix/ix_test.go | 54 ++-- adapters/jixie/jixie.go | 8 +- adapters/kidoz/kidoz.go | 10 +- adapters/kidoz/kidoz_test.go | 22 +- adapters/krushmedia/krushmedia.go | 14 +- adapters/kubient/kubient.go | 12 +- .../supplemental/bad_response.json | 2 +- adapters/lifestreet/lifestreet.go | 8 +- adapters/lifestreet/lifestreet_test.go | 36 +-- adapters/lockerdome/lockerdome.go | 10 +- .../supplemental/bad_response.json | 2 +- adapters/logicad/logicad.go | 22 +- adapters/lunamedia/lunamedia.go | 28 +- adapters/marsmedia/marsmedia.go | 10 +- adapters/mgid/mgid.go | 12 +- adapters/mobfoxpb/mobfoxpb.go | 14 +- .../supplemental/bad_response.json | 2 +- adapters/mobilefuse/mobilefuse.go | 18 +- adapters/nanointeractive/nanointeractive.go | 14 +- adapters/ninthdecimal/ninthdecimal.go | 28 +- adapters/nobid/nobid.go | 12 +- adapters/onetag/onetag.go | 12 +- adapters/openrtb_util.go | 51 ++- adapters/openrtb_util_test.go | 66 ++-- adapters/openx/openx.go | 22 +- adapters/openx/openx_test.go | 8 +- adapters/orbidder/orbidder.go | 12 +- adapters/pangle/pangle.go | 14 +- adapters/pubmatic/pubmatic.go | 36 +-- adapters/pubmatic/pubmatic_test.go | 46 +-- adapters/pubnative/pubnative.go | 22 +- adapters/pulsepoint/pulsepoint.go | 33 +- adapters/pulsepoint/pulsepoint_test.go | 18 +- .../supplemental/bad-bid-data.json | 2 +- adapters/revcontent/revcontent.go | 13 +- .../supplemental/bad_response.json | 2 +- adapters/rhythmone/rhythmone.go | 12 +- adapters/rtbhouse/rtbhouse.go | 8 +- .../supplemental/bad_response.json | 2 +- adapters/rubicon/rubicon.go | 52 ++- adapters/rubicon/rubicon_test.go | 223 ++++++------- adapters/sharethrough/butler.go | 18 +- adapters/sharethrough/butler_test.go | 80 ++--- adapters/sharethrough/sharethrough.go | 6 +- adapters/sharethrough/sharethrough_test.go | 36 +-- adapters/sharethrough/utils.go | 25 +- adapters/sharethrough/utils_test.go | 67 ++-- adapters/silvermob/silvermob.go | 16 +- .../supplemental/invalid-response.json | 2 +- adapters/smaato/smaato.go | 24 +- adapters/smartadserver/smartadserver.go | 16 +- adapters/smartrtb/smartrtb.go | 12 +- .../supplemental/invalid-bid-json.json | 2 +- adapters/smartyads/smartyads.go | 14 +- adapters/somoaudience/somoaudience.go | 27 +- adapters/sonobi/sonobi.go | 14 +- adapters/sovrn/sovrn.go | 14 +- adapters/sovrn/sovrn_test.go | 22 +- adapters/synacormedia/synacormedia.go | 16 +- .../supplemental/bad_response.json | 2 +- adapters/tappx/tappx.go | 10 +- adapters/telaria/telaria.go | 24 +- adapters/triplelift/triplelift.go | 14 +- .../triplelift_native/triplelift_native.go | 18 +- adapters/ucfunnel/ucfunnel.go | 14 +- adapters/ucfunnel/ucfunnel_test.go | 60 ++-- adapters/unicorn/unicorn.go | 20 +- adapters/unruly/unruly.go | 20 +- adapters/unruly/unruly_test.go | 64 ++-- adapters/valueimpression/valueimpression.go | 12 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/verizonmedia/verizonmedia.go | 20 +- adapters/visx/visx.go | 12 +- adapters/vrtcal/vrtcal.go | 8 +- adapters/yeahmobi/yeahmobi.go | 16 +- .../supplemental/bad_response.json | 2 +- adapters/yieldlab/yieldlab.go | 22 +- adapters/yieldmo/yieldmo.go | 14 +- adapters/yieldone/yieldone.go | 14 +- .../supplemental/bad_response.json | 2 +- adapters/zeroclickfraud/zeroclickfraud.go | 16 +- .../supplemental/bad-response-body.json | 2 +- amp/parse.go | 22 +- amp/parse_test.go | 12 +- analytics/config/config_test.go | 9 +- analytics/core.go | 14 +- analytics/filesystem/file_module_test.go | 7 +- analytics/pubstack/helpers/json_test.go | 9 +- analytics/pubstack/pubstack_module_test.go | 6 +- endpoints/auction.go | 4 +- endpoints/auction_test.go | 14 +- endpoints/openrtb2/amp_auction.go | 40 +-- endpoints/openrtb2/amp_auction_test.go | 114 +++---- endpoints/openrtb2/auction.go | 260 +++++++++++---- endpoints/openrtb2/auction_test.go | 302 +++++++++--------- endpoints/openrtb2/interstitial.go | 20 +- endpoints/openrtb2/interstitial_test.go | 14 +- .../audio-maxbitrate-negative.json | 28 ++ .../invalid-whole/audio-maxseq-negative.json | 28 ++ .../audio-minbitrate-negative.json | 28 ++ .../audio-sequence-negative.json | 28 ++ .../invalid-whole/banner-h-negative.json | 23 ++ .../invalid-whole/banner-w-negative.json | 23 ++ .../device-geo-accuracy-negative.json | 30 ++ .../invalid-whole/device-h-negative.json | 27 ++ .../invalid-whole/device-ppi-negative.json | 28 ++ .../invalid-whole/device-w-negative.json | 27 ++ .../invalid-whole/format-h-negative.json | 20 ++ .../invalid-whole/format-hratio-negative.json | 21 ++ .../invalid-whole/format-w-negative.json | 20 ++ .../invalid-whole/format-wmin-negative.json | 21 ++ .../invalid-whole/format-wratio-negative.json | 21 ++ .../user-geo-accuracy-negative.json | 28 ++ .../invalid-whole/video-h-negative.json | 28 ++ .../video-maxbitrate-negative.json | 28 ++ .../video-minbitrate-negative.json | 28 ++ .../invalid-whole/video-w-negative.json | 28 ++ endpoints/openrtb2/video_auction.go | 20 +- endpoints/openrtb2/video_auction_test.go | 136 ++++---- exchange/adapter_util_test.go | 8 +- exchange/auction.go | 20 +- exchange/auction_test.go | 24 +- exchange/bidder.go | 18 +- exchange/bidder_test.go | 83 +++-- exchange/bidder_validate_bids.go | 6 +- exchange/bidder_validate_bids_test.go | 38 +-- exchange/events_test.go | 6 +- exchange/exchange.go | 38 +-- exchange/exchange_test.go | 290 ++++++++--------- exchange/gdpr.go | 6 +- exchange/gdpr_test.go | 28 +- exchange/legacy.go | 32 +- exchange/legacy_test.go | 88 ++--- exchange/targeting.go | 6 +- exchange/targeting_test.go | 46 +-- exchange/utils.go | 36 +-- exchange/utils_test.go | 142 ++++---- go.mod | 5 +- go.sum | 18 +- openrtb_ext/bid_request_video.go | 18 +- openrtb_ext/deal_tier.go | 4 +- openrtb_ext/deal_tier_test.go | 4 +- openrtb_ext/response.go | 6 +- pbs/pbsrequest.go | 54 ++-- pbs/pbsresponse.go | 4 +- prebid_cache_client/client.go | 2 +- prebid_cache_client/prebid_cache.go | 4 +- privacy/ccpa/consentwriter.go | 6 +- privacy/ccpa/consentwriter_test.go | 20 +- privacy/ccpa/parsedpolicy_test.go | 4 +- privacy/ccpa/policy.go | 18 +- privacy/ccpa/policy_test.go | 144 ++++----- privacy/enforcement.go | 8 +- privacy/enforcement_test.go | 22 +- privacy/gdpr/consentwriter.go | 7 +- privacy/gdpr/consentwriter_test.go | 40 +-- privacy/lmt/ios.go | 14 +- privacy/lmt/ios_test.go | 140 ++++---- privacy/lmt/policy.go | 6 +- privacy/lmt/policy_test.go | 14 +- privacy/scrubber.go | 16 +- privacy/scrubber_test.go | 78 ++--- privacy/writer.go | 8 +- privacy/writer_test.go | 6 +- router/router.go | 2 +- 247 files changed, 3701 insertions(+), 3104 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index a48c18e9f58..bc7229eda99 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -51,7 +51,7 @@ type bidTtxExt struct { } // MakeRequests create the object for TTX Reqeust. -func (a *TtxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -77,14 +77,14 @@ func (a *TtxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters return adapterRequests, errs } -func (a *TtxAdapter) makeRequest(request openrtb.BidRequest, imp openrtb.Imp) (*adapters.RequestData, error) { +func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { impCopy, err := makeImps(imp) if err != nil { return nil, err } - request.Imp = []openrtb.Imp{*impCopy} + request.Imp = []openrtb2.Imp{*impCopy} // Last Step reqJSON, err := json.Marshal(request) @@ -103,7 +103,7 @@ func (a *TtxAdapter) makeRequest(request openrtb.BidRequest, imp openrtb.Imp) (* }, nil } -func makeImps(imp openrtb.Imp) (*openrtb.Imp, error) { +func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { if imp.Banner == nil && imp.Video == nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), @@ -158,7 +158,7 @@ func makeImps(imp openrtb.Imp) (*openrtb.Imp, error) { return &imp, nil } -func makeReqExt(request *openrtb.BidRequest) ([]byte, error) { +func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { var reqExt reqExt if len(request.Ext) > 0 { @@ -181,7 +181,7 @@ func makeReqExt(request *openrtb.BidRequest) ([]byte, error) { } // MakeBids make the bids for the bid response. -func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -198,7 +198,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -227,7 +227,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } -func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { +func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) { videoCopy := *video if videoCopy.W == 0 || videoCopy.H == 0 || @@ -248,7 +248,7 @@ func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, err videoCopy.Placement = 1 if videoCopy.StartDelay == nil { - videoCopy.StartDelay = openrtb.StartDelay.Ptr(0) + videoCopy.StartDelay = openrtb2.StartDelay.Ptr(0) } } diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index 9c6f73c27f0..a461dada391 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -31,7 +31,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getHeaders(request *openrtb.BidRequest) http.Header { +func getHeaders(request *openrtb2.BidRequest) http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -55,7 +55,7 @@ func getHeaders(request *openrtb.BidRequest) http.Header { } func (a *AcuityAdsAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -87,7 +87,7 @@ func (a *AcuityAdsAdapter) MakeRequests( }}, nil } -func (a *AcuityAdsAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtAcuityAds, error) { +func (a *AcuityAdsAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAcuityAds, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -136,7 +136,7 @@ func (a *AcuityAdsAdapter) checkResponseStatusCodes(response *adapters.ResponseD } func (a *AcuityAdsAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -153,7 +153,7 @@ func (a *AcuityAdsAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -178,7 +178,7 @@ func (a *AcuityAdsAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/adapterstest/adapter_test_util.go b/adapters/adapterstest/adapter_test_util.go index a5b3bb8b3f1..0c51a74e6b6 100644 --- a/adapters/adapterstest/adapter_test_util.go +++ b/adapters/adapterstest/adapter_test_util.go @@ -8,13 +8,13 @@ import ( "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" ) // OrtbMockService Represents a scaffolded OpenRTB service. type OrtbMockService struct { Server *httptest.Server - LastBidRequest *openrtb.BidRequest + LastBidRequest *openrtb2.BidRequest LastHttpRequest *http.Request } @@ -30,8 +30,8 @@ func BidOnTags(tags string) map[string]bool { } // SampleBid Produces a sample bid based on params given. -func SampleBid(width *uint64, height *uint64, impId string, index int) openrtb.Bid { - return openrtb.Bid{ +func SampleBid(width *int64, height *int64, impId string, index int) openrtb2.Bid { + return openrtb2.Bid{ ID: "Bid-123", ImpID: fmt.Sprintf("div-adunit-%d", index), Price: 2.1, @@ -64,7 +64,7 @@ func VerifyBoolValue(value bool, expected bool, t *testing.T) { } // VerifyBannerSize helper function to assert banner size -func VerifyBannerSize(banner *openrtb.Banner, expectedWidth int, expectedHeight int, t *testing.T) { +func VerifyBannerSize(banner *openrtb2.Banner, expectedWidth int, expectedHeight int, t *testing.T) { VerifyIntValue(int(*(banner.W)), expectedWidth, t) VerifyIntValue(int(*(banner.H)), expectedHeight, t) } diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 1e3178ed23d..e33dbddaecf 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -7,7 +7,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -128,7 +128,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd } type testSpec struct { - BidRequest openrtb.BidRequest `json:"mockBidRequest"` + BidRequest openrtb2.BidRequest `json:"mockBidRequest"` HttpCalls []httpCall `json:"httpCalls"` BidResponses []expectedBidResponse `json:"expectedBidResponses"` MakeRequestErrors []testSpecExpectedError `json:"expectedMakeRequestsErrors"` @@ -281,7 +281,7 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec } // diffOrtbBids compares the actual Bid made by the adapter to the expectation from the JSON file. -func diffOrtbBids(t *testing.T, description string, actual *openrtb.Bid, expected json.RawMessage) { +func diffOrtbBids(t *testing.T, description string, actual *openrtb2.Bid, expected json.RawMessage) { if actual == nil { t.Errorf("Bidders cannot return nil Bids. %s was nil.", description) return diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 79bac580f0b..df294b56154 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -13,14 +13,13 @@ import ( "strconv" "strings" + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" "golang.org/x/net/context/ctxhttp" ) @@ -235,8 +234,8 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { BidderCode: r.bidderCode, Price: bid.Price, Adm: adm, - Width: bid.Width, - Height: bid.Height, + Width: int64(bid.Width), + Height: int64(bid.Height), DealId: bid.DealId, Creative_id: bid.CreativeId, CreativeMediaType: string(bidType), @@ -374,7 +373,7 @@ func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL } } -func (a *AdformAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { adformRequest, errors := openRtbToAdformRequest(request) if len(adformRequest.adUnits) == 0 { return nil, errors @@ -392,7 +391,7 @@ func (a *AdformAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt return requests, errors } -func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []error) { +func openRtbToAdformRequest(request *openrtb2.BidRequest) (*adformRequest, []error) { adUnits := make([]*adformAdUnit, 0, len(request.Imp)) errors := make([]error, 0, len(request.Imp)) secure := false @@ -529,35 +528,35 @@ func encodeEids(eids []openrtb_ext.ExtUserEid) string { return encodedEids } -func getIPSafely(device *openrtb.Device) string { +func getIPSafely(device *openrtb2.Device) string { if device == nil { return "" } return device.IP } -func getIFASafely(device *openrtb.Device) string { +func getIFASafely(device *openrtb2.Device) string { if device == nil { return "" } return device.IFA } -func getUASafely(device *openrtb.Device) string { +func getUASafely(device *openrtb2.Device) string { if device == nil { return "" } return device.UA } -func getBuyerUIDSafely(user *openrtb.User) string { +func getBuyerUIDSafely(user *openrtb2.User) string { if user == nil { return "" } return user.BuyerUID } -func (a *AdformAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdformAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -584,7 +583,7 @@ func (a *AdformAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, nil } -func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapters.BidderResponse { +func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb2.BidRequest) *adapters.BidderResponse { bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adformBids)) currency := bidResponse.Currency @@ -598,13 +597,13 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt continue } - openRtbBid := openrtb.Bid{ + openRtbBid := openrtb2.Bid{ ID: r.Imp[i].ID, ImpID: r.Imp[i].ID, Price: bid.Price, AdM: adm, - W: bid.Width, - H: bid.Height, + W: int64(bid.Width), + H: int64(bid.Height), DealID: bid.DealId, CrID: bid.CreativeId, } diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 558453e4030..f2056c499c5 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/pbs" @@ -17,7 +18,6 @@ import ( "fmt" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -172,7 +172,7 @@ func TestAdformBasicResponse(t *testing.T) { if bid.Price != tag.price { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) } - if bid.Width != adformTestData.width || bid.Height != adformTestData.height { + if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) } if bid.Adm != tag.content { @@ -249,7 +249,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest regs := getRegs() r.Regs = ®s - user := openrtb.User{ + user := openrtb2.User{ Ext: getUserExt(), } r.User = &user @@ -260,7 +260,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { prebidRequest := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 4), - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: requestData.deviceUA, IP: requestData.deviceIP, IFA: requestData.deviceIFA, @@ -271,10 +271,10 @@ func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer for i, tag := range requestData.tags { prebidRequest.AdUnits[i] = pbs.AdUnit{ Code: tag.code, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { - W: requestData.width, - H: requestData.height, + W: int64(requestData.width), + H: int64(requestData.height), }, }, Bids: []pbs.Bids{ @@ -332,16 +332,16 @@ func TestOpenRTBRequest(t *testing.T) { func TestOpenRTBIncorrectRequest(t *testing.T) { bidder := new(AdformAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ {ID: "incorrect-bidder-field", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`)}, {ID: "incorrect-adform-params", Ext: json.RawMessage(`{"bidder": { : "33" }}`)}, {ID: "mid-integer", Ext: json.RawMessage(`{"bidder": { "mid": 1.234 }}`)}, {ID: "mid-greater-then-zero", Ext: json.RawMessage(`{"bidder": { "mid": -1 }}`)}, }, - Device: &openrtb.Device{UA: "ua", IP: "ip"}, - User: &openrtb.User{BuyerUID: "buyerUID"}, + Device: &openrtb2.Device{UA: "ua", IP: "ip"}, + User: &openrtb2.User{BuyerUID: "buyerUID"}, } httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -374,36 +374,36 @@ func createTestData(secure bool) aBidInfo { return testData } -func createOpenRtbRequest(testData *aBidInfo) *openrtb.BidRequest { +func createOpenRtbRequest(testData *aBidInfo) *openrtb2.BidRequest { secure := int8(0) if testData.secure { secure = int8(1) } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: make([]openrtb.Imp, len(testData.tags)), - Site: &openrtb.Site{ + Imp: make([]openrtb2.Imp, len(testData.tags)), + Site: &openrtb2.Site{ Page: testData.referrer, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: testData.deviceUA, IP: testData.deviceIP, IFA: testData.deviceIFA, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: testData.tid, }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: testData.buyerUID, }, } for i, tag := range testData.tags { - bidRequest.Imp[i] = openrtb.Imp{ + bidRequest.Imp[i] = openrtb2.Imp{ ID: tag.code, Secure: &secure, Ext: json.RawMessage(fmt.Sprintf("{\"bidder\": %s}", formatAdUnitJson(tag))), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } } @@ -457,7 +457,7 @@ func TestOpenRTBStandardResponse(t *testing.T) { if bid.Price != tag.price { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) } - if bid.W != testData.width || bid.H != testData.height { + if bid.W != int64(testData.width) || bid.H != int64(testData.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.W, bid.H, testData.width, testData.height) } if bid.AdM != tag.content { @@ -511,12 +511,12 @@ func TestAdformProperties(t *testing.T) { // helpers -func getRegs() openrtb.Regs { +func getRegs() openrtb2.Regs { var gdpr int8 = 1 regsExt := openrtb_ext.ExtRegs{ GDPR: &gdpr, } - regs := openrtb.Regs{} + regs := openrtb2.Regs{} regsExtData, err := json.Marshal(regsExt) if err == nil { regs.Ext = regsExtData @@ -689,37 +689,37 @@ func TestToOpenRtbBidResponse(t *testing.T) { expectedBids := 4 lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", "" - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "banner-imp-no1", Ext: json.RawMessage(`{"bidder1": { "mid": "32341" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no2", Ext: json.RawMessage(`{"bidder1": { "mid": "32342" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no3", Ext: json.RawMessage(`{"bidder1": { "mid": "32343" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no4", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "video-imp-no4", Ext: json.RawMessage(`{"bidder1": { "mid": "32345" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, }, - Device: &openrtb.Device{UA: "ua", IP: "ip"}, - User: &openrtb.User{BuyerUID: "buyerUID"}, + Device: &openrtb2.Device{UA: "ua", IP: "ip"}, + User: &openrtb2.User{BuyerUID: "buyerUID"}, } testAdformBids := []*adformBid{ diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index b726d0c103a..fd87c6f4034 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -41,7 +41,7 @@ type adgServerResponse struct { Results []interface{} `json:"results"` } -func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adg *AdgenerationAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) var errs []error @@ -79,7 +79,7 @@ func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInf return bidRequestArray, errs } -func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index int) (string, error) { +func (adg *AdgenerationAdapter) getRequestUri(request *openrtb2.BidRequest, index int) (string, error) { imp := request.Imp[index] adgExt, err := unmarshalExtImpAdgeneration(&imp) if err != nil { @@ -98,7 +98,7 @@ func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index return uriObj.String(), err } -func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidRequest, imp *openrtb.Imp) *url.Values { +func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb2.BidRequest, imp *openrtb2.Imp) *url.Values { v := url.Values{} v.Set("posall", "SSPLOC") v.Set("id", id) @@ -121,7 +121,7 @@ func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidReque return &v } -func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { +func unmarshalExtImpAdgeneration(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { var bidderExt adapters.ExtImpBidder var adgExt openrtb_ext.ExtImpAdgeneration if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { @@ -136,13 +136,13 @@ func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgenerat return &adgExt, nil } -func getSizes(imp *openrtb.Imp) string { +func getSizes(imp *openrtb2.Imp) string { if imp.Banner == nil || len(imp.Banner.Format) == 0 { return "" } var sizeStr string for _, v := range imp.Banner.Format { - sizeStr += strconv.FormatUint(v.W, 10) + "x" + strconv.FormatUint(v.H, 10) + "," + sizeStr += strconv.FormatInt(v.W, 10) + "x" + strconv.FormatInt(v.H, 10) + "," } if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 { sizeStr = sizeStr[:len(sizeStr)-1] @@ -150,7 +150,7 @@ func getSizes(imp *openrtb.Imp) string { return sizeStr } -func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string { +func (adg *AdgenerationAdapter) getCurrency(request *openrtb2.BidRequest) string { if len(request.Cur) <= 0 { return adg.defaultCurrency } else { @@ -163,7 +163,7 @@ func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string } } -func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -202,13 +202,13 @@ func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex impId = v.ID bitType = openrtb_ext.BidTypeBanner adm = createAd(&bidResp, impId) - bid := openrtb.Bid{ + bid := openrtb2.Bid{ ID: bidResp.Locationid, ImpID: impId, AdM: adm, Price: bidResp.Cpm, - W: bidResp.W, - H: bidResp.H, + W: int64(bidResp.W), + H: int64(bidResp.H), CrID: bidResp.Creativeid, DealID: bidResp.Dealid, } diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index f63c99cb92d..01fff96ede1 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -34,27 +34,27 @@ func TestgetRequestUri(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) // Test items - failedRequest := &openrtb.BidRequest{ + failedRequest := &openrtb2.BidRequest{ ID: "test-failed-bid-request", - Imp: []openrtb.Imp{ - {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)}, - {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)}, - {ID: "extImpAdgeneration-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "extImpBidder-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)}, + {ID: "extImpBidder-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)}, + {ID: "extImpAdgeneration-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)}, }, - Source: &openrtb.Source{TID: "SourceTID"}, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Source: &openrtb2.Source{TID: "SourceTID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } - successRequest := &openrtb.BidRequest{ + successRequest := &openrtb2.BidRequest{ ID: "test-success-bid-request", - Imp: []openrtb.Imp{ - {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, }, - Source: &openrtb.Source{TID: "SourceTID"}, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Source: &openrtb2.Source{TID: "SourceTID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } numRequests := len(failedRequest.Imp) @@ -108,23 +108,23 @@ func TestgetRequestUri(t *testing.T) { func TestGetSizes(t *testing.T) { // Test items - var request *openrtb.Imp + var request *openrtb2.Imp var size string - multiFormatBanner := &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 320, H: 50}}} - noFormatBanner := &openrtb.Banner{Format: []openrtb.Format{}} - nativeFormat := &openrtb.Native{} + multiFormatBanner := &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 320, H: 50}}} + noFormatBanner := &openrtb2.Banner{Format: []openrtb2.Format{}} + nativeFormat := &openrtb2.Native{} - request = &openrtb.Imp{Banner: multiFormatBanner} + request = &openrtb2.Imp{Banner: multiFormatBanner} size = getSizes(request) if size != "300x250,320x50" { t.Errorf("%v does not match size.", multiFormatBanner) } - request = &openrtb.Imp{Banner: noFormatBanner} + request = &openrtb2.Imp{Banner: noFormatBanner} size = getSizes(request) if size != "" { t.Errorf("%v does not match size.", noFormatBanner) } - request = &openrtb.Imp{Native: nativeFormat} + request = &openrtb2.Imp{Native: nativeFormat} size = getSizes(request) if size != "" { t.Errorf("%v does not match size.", nativeFormat) @@ -142,17 +142,17 @@ func TestGetCurrency(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) // Test items - var request *openrtb.BidRequest + var request *openrtb2.BidRequest var currency string innerDefaultCur := []string{"USD", "JPY"} usdCur := []string{"USD", "EUR"} - request = &openrtb.BidRequest{Cur: innerDefaultCur} + request = &openrtb2.BidRequest{Cur: innerDefaultCur} currency = bidderAdgeneration.getCurrency(request) if currency != "JPY" { t.Errorf("%v does not match currency.", innerDefaultCur) } - request = &openrtb.BidRequest{Cur: usdCur} + request = &openrtb2.BidRequest{Cur: usdCur} currency = bidderAdgeneration.getCurrency(request) if currency != "USD" { t.Errorf("%v does not match currency.", usdCur) @@ -212,14 +212,14 @@ func TestMakeBids(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) - internalRequest := &openrtb.BidRequest{ + internalRequest := &openrtb2.BidRequest{ ID: "test-success-bid-request", - Imp: []openrtb.Imp{ - {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, }, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } externalRequest := adapters.RequestData{} response := adapters.ResponseData{ @@ -254,8 +254,8 @@ func checkBidResponse(t *testing.T, bidderResponse *adapters.BidderResponse, exp var expectedID string = "58278" var expectedImpID = "bidRequest-success-test" var expectedPrice float64 = 30.0 - var expectedW uint64 = 300 - var expectedH uint64 = 250 + var expectedW int64 = 300 + var expectedH int64 = 250 var expectedCrID string = "Dummy_supership.jp" var extectedDealID string = "test-deal-id" diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index e13c03a101d..773b3b98fd6 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -11,7 +11,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -57,7 +57,7 @@ func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string { return parametersAsString } -func extractGdprParameter(request *openrtb.BidRequest) string { +func extractGdprParameter(request *openrtb2.BidRequest) string { if request.User != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { @@ -67,21 +67,21 @@ func extractGdprParameter(request *openrtb.BidRequest) string { return "" } -func extractRefererParameter(request *openrtb.BidRequest) string { +func extractRefererParameter(request *openrtb2.BidRequest) string { if request.Site != nil && request.Site.Page != "" { return "/xf" + url.QueryEscape(request.Site.Page) } return "" } -func extractIfaParameter(request *openrtb.BidRequest) string { +func extractIfaParameter(request *openrtb2.BidRequest) string { if request.Device != nil && request.Device.IFA != "" { return "/xz" + url.QueryEscape(request.Device.IFA) } return "" } -func (a *AdheseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error @@ -128,14 +128,14 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } -func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } else if response.StatusCode != http.StatusOK { return nil, []error{WrapServerError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse var adheseBidResponseArray []AdheseBid if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { @@ -163,11 +163,11 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Price %v as float ", string(adheseBid.Extension.Prebid.Cpm.Amount)))} } - width, err := strconv.ParseUint(adheseBid.Width, 10, 64) + width, err := strconv.ParseInt(adheseBid.Width, 10, 64) if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Width %v as int ", string(adheseBid.Width)))} } - height, err := strconv.ParseUint(adheseBid.Height, 10, 64) + height, err := strconv.ParseInt(adheseBid.Height, 10, 64) if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Height %v as int ", string(adheseBid.Height)))} } @@ -198,16 +198,16 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidderResponse, errs } -func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb.BidResponse { +func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb2.BidResponse { adheseExtJson, err := json.Marshal(adheseOriginData) if err != nil { glog.Error(fmt.Sprintf("Unable to parse adhese Origin Data as JSON due to %v", err)) adheseExtJson = make([]byte, 0) } - return openrtb.BidResponse{ + return openrtb2.BidResponse{ ID: adheseExt.Id, - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ DealID: adheseExt.OrderId, CrID: adheseExt.Id, AdM: getAdMarkup(adheseBid, adheseExt), @@ -218,8 +218,8 @@ func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData } } -func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb.BidResponse { - var response openrtb.BidResponse = adheseBid.OriginData +func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb2.BidResponse { + var response openrtb2.BidResponse = adheseBid.OriginData if len(response.SeatBid) > 0 && len(response.SeatBid[0].Bid) > 0 { response.SeatBid[0].Bid[0].AdM = adheseBid.Body } diff --git a/adapters/adhese/utils.go b/adapters/adhese/utils.go index 9983be96d38..c83f9ef1480 100644 --- a/adapters/adhese/utils.go +++ b/adapters/adhese/utils.go @@ -1,6 +1,6 @@ package adhese -import "github.com/mxmCherry/openrtb" +import "github.com/mxmCherry/openrtb/v14/openrtb2" type AdheseOriginData struct { Priority string `json:"priority"` @@ -22,13 +22,13 @@ type AdheseExt struct { } type AdheseBid struct { - Origin string `json:"origin"` - OriginData openrtb.BidResponse `json:"originData"` - OriginInstance string `json:"originInstance,omitempty"` - Body string `json:"body,omitempty"` - Height string `json:"height"` - Width string `json:"width"` - Extension Prebid `json:"extension"` + Origin string `json:"origin"` + OriginData openrtb2.BidResponse `json:"originData"` + OriginInstance string `json:"originInstance,omitempty"` + Body string `json:"body,omitempty"` + Height string `json:"height"` + Width string `json:"width"` + Extension Prebid `json:"extension"` } type Prebid struct { diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 6bedeec612f..93fb3c6f93d 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type adkernelAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *adkernelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *adkernelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, newBadInputError("No impression in the bid request")) @@ -52,10 +52,10 @@ func (adapter *adkernelAdapter) MakeRequests(request *openrtb.BidRequest, reqInf } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdkernel, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdkernel, []error) { impsCount := len(imps) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) resImpExts := make([]openrtb_ext.ExtImpAdkernel, 0, impsCount) for _, imp := range imps { @@ -74,7 +74,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernel) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernel) error { if impExt.ZoneId < 1 { return newBadInputError(fmt.Sprintf("Invalid zoneId value: %d. Ignoring imp id=%s", impExt.ZoneId, imp.ID)) } @@ -88,8 +88,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernel) er } //Group impressions by AdKernel-specific parameters `zoneId` & `host` -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkernel) (map[openrtb_ext.ExtImpAdkernel][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdkernel][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernel) (map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp) errors := make([]error, 0) for idx := range imps { imp := imps[idx] @@ -100,7 +100,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) } @@ -108,7 +108,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } //Alter impression info to comply with adkernel platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -119,21 +119,21 @@ func compatImpression(imp *openrtb.Imp) error { return newBadInputError("Invalid impression") } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { imp.Audio = nil imp.Video = nil imp.Native = nil return nil } -func compatVideoImpression(imp *openrtb.Imp) error { +func compatVideoImpression(imp *openrtb2.Imp) error { imp.Banner = nil imp.Audio = nil imp.Native = nil return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernel, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernel, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -149,7 +149,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernel, error) { return &adkernelExt, nil } -func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -173,7 +173,7 @@ func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb.Bi Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps if bidRequest.Site != nil { @@ -198,7 +198,7 @@ func (adapter *adkernelAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdker } //MakeBids translates adkernel bid response to prebid-server specific format -func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -207,7 +207,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), } } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), @@ -233,7 +233,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { return openrtb_ext.BidTypeBanner diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index acd27c9e894..01024baad61 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,7 +22,7 @@ type adkernelAdnAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, newBadInputError("No impression in the bid request")) @@ -54,10 +54,10 @@ func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb.BidRequest, req } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdkernelAdn, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdkernelAdn, []error) { impsCount := len(imps) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) resImpExts := make([]openrtb_ext.ExtImpAdkernelAdn, 0, impsCount) for _, imp := range imps { @@ -77,7 +77,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) error { if impExt.PublisherID < 1 { return newBadInputError(fmt.Sprintf("Invalid pubId value. Ignoring imp id=%s", imp.ID)) } @@ -88,8 +88,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) } //Group impressions by AdKernel-specific parameters `pubId` & `host` -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp) errors := make([]error, 0) for idx := range imps { imp := imps[idx] @@ -100,7 +100,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) @@ -109,7 +109,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } //Alter impression info to comply with adkernel platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -120,7 +120,7 @@ func compatImpression(imp *openrtb.Imp) error { return newBadInputError("Unsupported impression has been received") } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner banner := &bannerCopy @@ -143,14 +143,14 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func compatVideoImpression(imp *openrtb.Imp) error { +func compatVideoImpression(imp *openrtb2.Imp) error { imp.Banner = nil imp.Audio = nil imp.Native = nil return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -166,7 +166,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) return &adkernelAdnExt, nil } -func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -190,7 +190,7 @@ func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps @@ -222,7 +222,7 @@ func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAd } //MakeBids translates adkernel bid response to prebid-server specific format -func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -231,7 +231,7 @@ func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), } } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), @@ -257,7 +257,7 @@ func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { return openrtb_ext.BidTypeBanner diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index e0f67db816f..0eab6204b6d 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,7 +30,7 @@ type admanParams struct { } // MakeRequests create bid request for adman demand -func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var admanExt openrtb_ext.ExtImpAdman var err error @@ -39,7 +39,7 @@ func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} var bidderExt adapters.ExtImpBidder if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { @@ -63,7 +63,7 @@ func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } -func (a *AdmanAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdmanAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -86,7 +86,7 @@ func (a *AdmanAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reque } // MakeBids makes the bids -func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdmanAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -99,7 +99,7 @@ func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -124,7 +124,7 @@ func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/adman/admantest/supplemental/bad_response.json b/adapters/adman/admantest/supplemental/bad_response.json index d5a28c74256..4431a328154 100644 --- a/adapters/adman/admantest/supplemental/bad_response.json +++ b/adapters/adman/admantest/supplemental/bad_response.json @@ -78,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index eff93746df2..86d7bfd9d5f 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -28,7 +28,7 @@ type admixerImpExt struct { CustomParams map[string]interface{} `json:"customParams"` } -func (a *AdmixerAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { +func (a *AdmixerAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { rq, errs := a.makeRequest(request) if len(errs) > 0 { @@ -43,9 +43,9 @@ func (a *AdmixerAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return } -func (a *AdmixerAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdmixerAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ @@ -84,7 +84,7 @@ func (a *AdmixerAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req }, errs } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -126,7 +126,7 @@ func preprocess(imp *openrtb.Imp) error { return nil } -func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -149,7 +149,7 @@ func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -172,7 +172,7 @@ func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 43aa067b3a9..97ace405ab0 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -13,7 +13,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -80,7 +80,7 @@ type AdOceanAdapter struct { measurementCode string } -func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdOceanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ Message: "No impression in the bid request", @@ -119,8 +119,8 @@ func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap func (a *AdOceanAdapter) addNewBid( requestsData []*requestData, - imp *openrtb.Imp, - request *openrtb.BidRequest, + imp *openrtb2.Imp, + request *openrtb2.BidRequest, consentString string, ) ([]*requestData, error) { var bidderExt adapters.ExtImpBidder @@ -177,7 +177,7 @@ func (a *AdOceanAdapter) addNewBid( return requestsData, nil } -func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.ExtImpAdOcean, imp *openrtb.Imp, testImp bool) bool { +func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.ExtImpAdOcean, imp *openrtb2.Imp, testImp bool) bool { auctionID := imp.ID for _, requestData := range requestsData { @@ -209,8 +209,8 @@ func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.Ex func (a *AdOceanAdapter) makeURL( params *openrtb_ext.ExtImpAdOcean, - imp *openrtb.Imp, - request *openrtb.BidRequest, + imp *openrtb2.Imp, + request *openrtb2.BidRequest, slaveSizes map[string]string, consentString string, ) (*url.URL, error) { @@ -256,7 +256,7 @@ func (a *AdOceanAdapter) makeURL( return endpointURL, nil } -func getImpSizes(imp *openrtb.Imp) string { +func getImpSizes(imp *openrtb2.Imp) string { if imp.Banner == nil { return "" } @@ -264,14 +264,14 @@ func getImpSizes(imp *openrtb.Imp) string { if len(imp.Banner.Format) > 0 { sizes := make([]string, len(imp.Banner.Format)) for i, format := range imp.Banner.Format { - sizes[i] = strconv.FormatUint(format.W, 10) + "x" + strconv.FormatUint(format.H, 10) + sizes[i] = strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10) } return strings.Join(sizes, "_") } if imp.Banner.W != nil && imp.Banner.H != nil { - return strconv.FormatUint(*imp.Banner.W, 10) + "x" + strconv.FormatUint(*imp.Banner.H, 10) + return strconv.FormatInt(*imp.Banner.W, 10) + "x" + strconv.FormatInt(*imp.Banner.H, 10) } return "" @@ -304,7 +304,7 @@ func setSlaveSizesParam(queryParams *url.Values, slaveSizes map[string]string, o } func (a *AdOceanAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -337,8 +337,8 @@ func (a *AdOceanAdapter) MakeBids( } price, _ := strconv.ParseFloat(bid.Price, 64) - width, _ := strconv.ParseUint(bid.Width, 10, 64) - height, _ := strconv.ParseUint(bid.Height, 10, 64) + width, _ := strconv.ParseInt(bid.Width, 10, 64) + height, _ := strconv.ParseInt(bid.Height, 10, 64) adCode, err := a.prepareAdCodeForBid(bid) if err != nil { errors = append(errors, err) @@ -346,7 +346,7 @@ func (a *AdOceanAdapter) MakeBids( } parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{ - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ID: bid.ID, ImpID: auctionID, Price: price, diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index adbb177d887..56fae4295b4 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -8,7 +8,7 @@ import ( "net/url" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -50,7 +50,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } func (ads *AdopplerAdapter) MakeRequests( - req *openrtb.BidRequest, + req *openrtb2.BidRequest, info *adapters.ExtraRequestInfo, ) ( []*adapters.RequestData, @@ -69,9 +69,9 @@ func (ads *AdopplerAdapter) MakeRequests( continue } - var r openrtb.BidRequest = *req + var r openrtb2.BidRequest = *req r.ID = req.ID + "-" + ext.AdUnit - r.Imp = []openrtb.Imp{imp} + r.Imp = []openrtb2.Imp{imp} body, err := json.Marshal(r) if err != nil { @@ -99,7 +99,7 @@ func (ads *AdopplerAdapter) MakeRequests( } func (ads *AdopplerAdapter) MakeBids( - intReq *openrtb.BidRequest, + intReq *openrtb2.BidRequest, extReq *adapters.RequestData, resp *adapters.ResponseData, ) ( @@ -119,7 +119,7 @@ func (ads *AdopplerAdapter) MakeBids( return nil, []error{err} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err := json.Unmarshal(resp.Body, &bidResp) if err != nil { err := &errortypes.BadServerResponse{ diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-response.json b/adapters/adoppler/adopplertest/supplemental/invalid-response.json index d0a7d2ef84b..83aa35f0ec1 100644 --- a/adapters/adoppler/adopplertest/supplemental/invalid-response.json +++ b/adapters/adoppler/adopplertest/supplemental/invalid-response.json @@ -49,7 +49,7 @@ ], "expectedMakeBidsErrors":[ { - "value":"invalid body: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value":"invalid body: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison":"literal" } ] diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index c58c4a1ee7a..f4fd6c23bdb 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -3,12 +3,13 @@ package adot import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb" + "net/http" + + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "net/http" ) type adapter struct { @@ -32,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var reqJSON []byte var err error @@ -53,7 +54,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex // MakeBids unpacks the server's response into Bids. // The bidder return a status code 204 when it cannot delivery an ad. -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -70,7 +71,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -96,7 +97,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest } // getMediaTypeForBid determines which type of bid. -func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) { +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { if bid == nil { return "", fmt.Errorf("the bid request object is nil") } diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index fca6b303626..3cb3d680ebd 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -2,13 +2,14 @@ package adot import ( "encoding/json" - "github.com/mxmCherry/openrtb" + "testing" + + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" - "testing" ) const testsBidderEndpoint = "https://dsp.adotmob.com/headerbidding/bidrequest" @@ -31,7 +32,7 @@ func TestMediaTypeError(t *testing.T) { assert.Error(t, err) byteInvalid, _ := json.Marshal(&adotBidExt{Adot: bidExt{"invalid"}}) - _, err = getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteInvalid)}) + _, err = getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteInvalid)}) assert.Error(t, err) } @@ -59,17 +60,17 @@ func TestMediaTypeForBid(t *testing.T) { byteVideo, _ := json.Marshal(&adotBidExt{Adot: bidExt{"video"}}) byteNative, _ := json.Marshal(&adotBidExt{Adot: bidExt{"native"}}) - bidTypeBanner, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteBanner)}) + bidTypeBanner, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteBanner)}) if bidTypeBanner != openrtb_ext.BidTypeBanner { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeBanner, openrtb_ext.BidTypeBanner) } - bidTypeVideo, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteVideo)}) + bidTypeVideo, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteVideo)}) if bidTypeVideo != openrtb_ext.BidTypeVideo { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeVideo, openrtb_ext.BidTypeVideo) } - bidTypeNative, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteNative)}) + bidTypeNative, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteNative)}) if bidTypeNative != openrtb_ext.BidTypeNative { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeNative, openrtb_ext.BidTypeVideo) } diff --git a/adapters/adot/adottest/supplemental/unmarshal_error.json b/adapters/adot/adottest/supplemental/unmarshal_error.json index a87e1189a62..3094fa865e4 100644 --- a/adapters/adot/adottest/supplemental/unmarshal_error.json +++ b/adapters/adot/adottest/supplemental/unmarshal_error.json @@ -54,7 +54,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index 7907ceb1891..ffc36cc5aba 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -5,10 +5,10 @@ import ( "fmt" "net/http" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" ) @@ -26,7 +26,7 @@ type adponeAdapter struct { } func (adapter *adponeAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -75,7 +75,7 @@ const unexpectedStatusCodeFormat = "" + "Unexpected status code: %d. Run with request.debug = 1 for more info" func (adapter *adponeAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -99,7 +99,7 @@ func (adapter *adponeAdapter) MakeBids( return nil, []error{err} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/adpone/adponetest/supplemental/bad_response.json b/adapters/adpone/adponetest/supplemental/bad_response.json index 9cbc8b5d9c2..68da7064b97 100644 --- a/adapters/adpone/adponetest/supplemental/bad_response.json +++ b/adapters/adpone/adponetest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 0db9218fdb4..6489c543d56 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -27,7 +27,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests create bid request for adprime demand -func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdprimeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var err error var tagID string @@ -36,7 +36,7 @@ func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") if err != nil { @@ -55,7 +55,7 @@ func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return adapterRequests, errs } -func (a *AdprimeAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdprimeAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -78,7 +78,7 @@ func (a *AdprimeAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req } // MakeBids makes the bids -func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -97,7 +97,7 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -122,7 +122,7 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/adprime/adprimetest/supplemental/bad_response.json b/adapters/adprime/adprimetest/supplemental/bad_response.json index 329e9c7269f..435529a485c 100644 --- a/adapters/adprime/adprimetest/supplemental/bad_response.json +++ b/adapters/adprime/adprimetest/supplemental/bad_response.json @@ -78,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 511918bb3df..b43e7e51b07 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type adtargetImpExt struct { Adtarget openrtb_ext.ExtImpAdtarget `json:"adtarget"` } -func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdtargetAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { totalImps := len(request.Imp) errors := make([]error, 0, totalImps) @@ -55,7 +55,7 @@ func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada reqs := make([]*adapters.RequestData, 0, totalReqs) imps := request.Imp - request.Imp = make([]openrtb.Imp, 0, len(imps)) + request.Imp = make([]openrtb2.Imp, 0, len(imps)) for sourceId, impIndexes := range imp2source { request.Imp = request.Imp[:0] @@ -80,7 +80,7 @@ func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada return reqs, errors } -func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdtargetAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { if httpRes.StatusCode == http.StatusNoContent { return nil, nil @@ -90,7 +90,7 @@ func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters. Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", httpRes.StatusCode), }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), @@ -137,7 +137,7 @@ func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters. return bidResponse, errors } -func validateImpressionAndSetExt(imp *openrtb.Imp) (int, error) { +func validateImpressionAndSetExt(imp *openrtb2.Imp) (int, error) { if imp.Banner == nil && imp.Video == nil { return 0, &errortypes.BadInput{ diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index fd501fde01f..6c7bc1f3378 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type adtelligentImpExt struct { Adtelligent openrtb_ext.ExtImpAdtelligent `json:"adtelligent"` } -func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdtelligentAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { totalImps := len(request.Imp) errors := make([]error, 0, totalImps) @@ -55,7 +55,7 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * reqs := make([]*adapters.RequestData, 0, totalReqs) imps := request.Imp - request.Imp = make([]openrtb.Imp, 0, len(imps)) + request.Imp = make([]openrtb2.Imp, 0, len(imps)) for sourceId, impIds := range imp2source { request.Imp = request.Imp[:0] @@ -85,13 +85,13 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * } -func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { if httpRes.StatusCode == http.StatusNoContent { return nil, nil } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), @@ -138,7 +138,7 @@ func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapte return bidResponse, errors } -func validateImpression(imp *openrtb.Imp) (int, error) { +func validateImpression(imp *openrtb2.Imp) (int, error) { if imp.Banner == nil && imp.Video == nil { return 0, &errortypes.BadInput{ diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 95ddec0f452..8a12089df90 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type AdvangelistsAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) @@ -51,9 +51,9 @@ func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb.BidRequest, re } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdvangelists, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdvangelists, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) + resImps := make([]openrtb2.Imp, 0, len(imps)) resImpExts := make([]openrtb_ext.ExtImpAdvangelists, 0, len(imps)) for _, imp := range imps { @@ -73,7 +73,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdvangelists) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdvangelists) error { if impExt.PublisherID == "" { return &errortypes.BadInput{Message: "No pubid value provided"} } @@ -81,8 +81,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdvangelists } //Group impressions by advangelists-specific parameters `pubid -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvangelists) (map[openrtb_ext.ExtImpAdvangelists][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdvangelists][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdvangelists) (map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp) errors := make([]error, 0) for idx, imp := range imps { err := compatImpression(&imp) @@ -92,7 +92,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvange } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) @@ -101,7 +101,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvange } //Alter impression info to comply with advangelists platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to advangelists platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -109,7 +109,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner banner := &bannerCopy @@ -127,7 +127,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdvangelists, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdvangelists, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -143,7 +143,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdvangelists, error) return &advangelistsExt, nil } -func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -167,7 +167,7 @@ func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrt Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -197,7 +197,7 @@ func (adapter *AdvangelistsAdapter) buildEndpointURL(params *openrtb_ext.ExtImpA } //MakeBids translates advangelists bid response to prebid-server specific format -func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { return nil, nil @@ -206,7 +206,7 @@ func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} @@ -230,7 +230,7 @@ func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Video != nil { return openrtb_ext.BidTypeVideo diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index 9a137a41fef..4797341e7b5 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -9,7 +9,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" ) @@ -25,7 +25,7 @@ type adapter struct { } func (a *adapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -35,7 +35,7 @@ func (a *adapter) MakeRequests( var tagID string reqCopy := *openRTBRequest - reqCopy.Imp = []openrtb.Imp{} + reqCopy.Imp = []openrtb2.Imp{} for ind, imp := range openRTBRequest.Imp { reqCopy.Imp = append(reqCopy.Imp, imp) @@ -77,7 +77,7 @@ const unexpectedStatusCodeFormat = "" + "Unexpected status code: %d. Run with request.debug = 1 for more info" func (a *adapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -101,7 +101,7 @@ func (a *adapter) MakeBids( return nil, []error{err} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } @@ -121,7 +121,7 @@ func (a *adapter) MakeBids( } // getMediaTypeForBid determines which type of bid. -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json index b156dd65ae3..bda8d3e6640 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json +++ b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json @@ -58,7 +58,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 07f9ad8bc96..1ff942ce4de 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,10 +16,10 @@ type AJAAdapter struct { endpoint string } -func (a *AJAAdapter) MakeRequests(bidReq *openrtb.BidRequest, extraInfo *adapters.ExtraRequestInfo) (adapterReqs []*adapters.RequestData, errs []error) { +func (a *AJAAdapter) MakeRequests(bidReq *openrtb2.BidRequest, extraInfo *adapters.ExtraRequestInfo) (adapterReqs []*adapters.RequestData, errs []error) { // split imps by tagid tagIDs := []string{} - impsByTagID := map[string][]openrtb.Imp{} + impsByTagID := map[string][]openrtb2.Imp{} for _, imp := range bidReq.Imp { extAJA, err := parseExtAJA(imp) if err != nil { @@ -54,7 +54,7 @@ func (a *AJAAdapter) MakeRequests(bidReq *openrtb.BidRequest, extraInfo *adapter return } -func parseExtAJA(imp openrtb.Imp) (openrtb_ext.ExtImpAJA, error) { +func parseExtAJA(imp openrtb2.Imp) (openrtb_ext.ExtImpAJA, error) { var ( extImp adapters.ExtImpBidder extAJA openrtb_ext.ExtImpAJA @@ -75,7 +75,7 @@ func parseExtAJA(imp openrtb.Imp) (openrtb_ext.ExtImpAJA, error) { return extAJA, nil } -func (a *AJAAdapter) MakeBids(bidReq *openrtb.BidRequest, adapterReq *adapters.RequestData, adapterResp *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AJAAdapter) MakeBids(bidReq *openrtb2.BidRequest, adapterReq *adapters.RequestData, adapterResp *adapters.ResponseData) (*adapters.BidderResponse, []error) { if adapterResp.StatusCode != http.StatusOK { if adapterResp.StatusCode == http.StatusNoContent { return nil, nil @@ -90,7 +90,7 @@ func (a *AJAAdapter) MakeBids(bidReq *openrtb.BidRequest, adapterReq *adapters.R }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(adapterResp.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Failed to unmarshal bid response: %s", err.Error()), diff --git a/adapters/amx/amx.go b/adapters/amx/amx.go index a9dfd06f1b0..68f4ecdfbb4 100644 --- a/adapters/amx/amx.go +++ b/adapters/amx/amx.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -49,9 +49,9 @@ type amxExt struct { Bidder openrtb_ext.ExtImpAMX `json:"bidder"` } -func ensurePublisherWithID(pub *openrtb.Publisher, publisherID string) openrtb.Publisher { +func ensurePublisherWithID(pub *openrtb2.Publisher, publisherID string) openrtb2.Publisher { if pub == nil { - return openrtb.Publisher{ID: publisherID} + return openrtb2.Publisher{ID: publisherID} } pubCopy := *pub @@ -60,7 +60,7 @@ func ensurePublisherWithID(pub *openrtb.Publisher, publisherID string) openrtb.P } // MakeRequests creates AMX adapter requests -func (adapter *AMXAdapter) MakeRequests(request *openrtb.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { +func (adapter *AMXAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { reqCopy := *request var publisherID string @@ -119,7 +119,7 @@ type amxBidExt struct { } // MakeBids will parse the bids from the AMX server -func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *AMXAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if http.StatusNoContent == response.StatusCode { @@ -140,7 +140,7 @@ func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -196,7 +196,7 @@ func pixelToImpression(pixel string) string { return fmt.Sprintf(vastImpressionFormat, pixel) } -func interpolateImpressions(bid openrtb.Bid, ext amxBidExt) string { +func interpolateImpressions(bid openrtb2.Bid, ext amxBidExt) string { var buffer strings.Builder if bid.NURL != "" { buffer.WriteString(pixelToImpression(bid.NURL)) diff --git a/adapters/amx/amx_test.go b/adapters/amx/amx_test.go index 8f05aec24d4..77947c068d0 100644 --- a/adapters/amx/amx_test.go +++ b/adapters/amx/amx_test.go @@ -6,7 +6,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -48,7 +48,7 @@ func TestEndpointQueryStringMalformed(t *testing.T) { func TestMakeRequestsTagID(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ Endpoint: amxTestEndpoint}) @@ -75,12 +75,12 @@ func TestMakeRequestsTagID(t *testing.T) { } for _, tc := range tests { - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "sample_imp_1", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} @@ -94,16 +94,16 @@ func TestMakeRequestsTagID(t *testing.T) { imp1.TagID = tc.tagID } - inputRequest := openrtb.BidRequest{ - User: &openrtb.User{}, - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{}, + inputRequest := openrtb2.BidRequest{ + User: &openrtb2.User{}, + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{}, } actualAdapterRequests, err := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) assert.Len(t, actualAdapterRequests, 1) assert.Empty(t, err) - var body openrtb.BidRequest + var body openrtb2.BidRequest assert.Nil(t, json.Unmarshal(actualAdapterRequests[0].Body, &body)) assert.Equal(t, tc.expectedTagID, body.Imp[0].TagID) } @@ -111,7 +111,7 @@ func TestMakeRequestsTagID(t *testing.T) { func TestMakeRequestsPublisherId(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ Endpoint: amxTestEndpoint}) @@ -137,12 +137,12 @@ func TestMakeRequestsPublisherId(t *testing.T) { } for _, tc := range tests { - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "sample_imp_1", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} @@ -152,15 +152,15 @@ func TestMakeRequestsPublisherId(t *testing.T) { fmt.Sprintf(`{"bidder":{"tagId":"%s"}}`, tc.extTagID)) } - inputRequest := openrtb.BidRequest{ - User: &openrtb.User{ID: "example_user_id"}, - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{}, + inputRequest := openrtb2.BidRequest{ + User: &openrtb2.User{ID: "example_user_id"}, + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{}, ID: "1234", } if tc.publisherID != "" || !tc.blankNil { - inputRequest.Site.Publisher = &openrtb.Publisher{ + inputRequest.Site.Publisher = &openrtb2.Publisher{ ID: tc.publisherID, } } @@ -168,7 +168,7 @@ func TestMakeRequestsPublisherId(t *testing.T) { actualAdapterRequests, err := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) assert.Len(t, actualAdapterRequests, 1) assert.Empty(t, err) - var body openrtb.BidRequest + var body openrtb2.BidRequest assert.Nil(t, json.Unmarshal(actualAdapterRequests[0].Body, &body)) assert.Equal(t, tc.expectedPublisherID, body.Site.Publisher.ID) } @@ -182,7 +182,7 @@ func countImpressionPixels(vast string) int { } func TestVideoImpInsertion(t *testing.T) { - markup := interpolateImpressions(openrtb.Bid{ + markup := interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "https://example2.com/nurl", }, amxBidExt{Himp: []string{"https://example.com/pixel.png"}}) @@ -191,14 +191,14 @@ func TestVideoImpInsertion(t *testing.T) { assert.Equal(t, 3, countImpressionPixels(markup), "should have 3 Impression pixels") // make sure that a blank NURL won't result in a blank impression tag - markup = interpolateImpressions(openrtb.Bid{ + markup = interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "", }, amxBidExt{}) assert.Equal(t, 1, countImpressionPixels(markup), "should have 1 impression pixels") // we should also ignore blank ext.Himp pixels - markup = interpolateImpressions(openrtb.Bid{ + markup = interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "https://example-nurl.com/nurl", }, amxBidExt{Himp: []string{"", "", ""}}) @@ -206,7 +206,7 @@ func TestVideoImpInsertion(t *testing.T) { } func TestNoDisplayImpInsertion(t *testing.T) { - data := interpolateImpressions(openrtb.Bid{ + data := interpolateImpressions(openrtb2.Bid{ AdM: sampleDisplayADM, NURL: "https://example2.com/nurl", }, amxBidExt{Himp: []string{"https://example.com/pixel.png"}}) diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go index fe5fdc17933..6edc8135516 100644 --- a/adapters/applogy/applogy.go +++ b/adapters/applogy/applogy.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type ApplogyAdapter struct { endpoint string } -func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ApplogyAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -41,8 +41,8 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E continue } banner := *impression.Banner - banner.W = openrtb.Uint64Ptr(banner.Format[0].W) - banner.H = openrtb.Uint64Ptr(banner.Format[0].H) + banner.W = openrtb2.Int64Ptr(banner.Format[0].W) + banner.H = openrtb2.Int64Ptr(banner.Format[0].H) impression.Banner = &banner } } @@ -70,7 +70,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E errs = append(errs, errors.New("Applogy token required")) continue } - request.Imp = []openrtb.Imp{impression} + request.Imp = []openrtb2.Imp{impression} body, err := json.Marshal(request) if err != nil { errs = append(errs, err) @@ -92,7 +92,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E return result, errs } -func (a *ApplogyAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ApplogyAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error switch responseData.StatusCode { @@ -110,7 +110,7 @@ func (a *ApplogyAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.Reque }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse err := json.Unmarshal(responseData.Body, &bidResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 0a8146fdc41..a384257d47b 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -12,12 +12,12 @@ import ( "strings" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/pbs" "golang.org/x/net/context/ctxhttp" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" @@ -163,9 +163,9 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } if anReq.Imp[i].Banner != nil && params.Position != "" { if params.Position == "above" { - anReq.Imp[i].Banner.Pos = openrtb.AdPositionAboveTheFold.Ptr() + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() } else if params.Position == "below" { - anReq.Imp[i].Banner.Pos = openrtb.AdPositionBelowTheFold.Ptr() + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() } } @@ -245,7 +245,7 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder debug.ResponseBody = responseBody } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, err @@ -288,7 +288,7 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder return bids, nil } -func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -389,9 +389,9 @@ func generatePodId() string { return fmt.Sprint(val) } -func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) { +func groupByPods(imps []openrtb2.Imp) map[string]([]openrtb2.Imp) { // find number of pods in response - podImps := make(map[string][]openrtb.Imp) + podImps := make(map[string][]openrtb2.Imp) for _, imp := range imps { pod := strings.Split(imp.ID, "_")[0] podImps[pod] = append(podImps[pod], imp) @@ -399,7 +399,7 @@ func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) { return podImps } -func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appnexusReqExt, errs []error) { +func marshalAndSetRequestExt(request *openrtb2.BidRequest, requestExtension appnexusReqExt, errs []error) { var err error request.Ext, err = json.Marshal(requestExtension) if err != nil { @@ -407,7 +407,7 @@ func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appne } } -func splitRequests(imps []openrtb.Imp, request *openrtb.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) { +func splitRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) { // Initial capacity for future array of requests, memory optimization. // Let's say there are 35 impressions and limit impressions per request equals to 10. @@ -463,7 +463,7 @@ func keys(m map[string]bool) []string { // preprocess mutates the imp to get it ready to send to appnexus. // // It returns the member param, if it exists, and an error if anything went wrong during the preprocessing. -func preprocess(imp *openrtb.Imp, defaultDisplayManagerVer string) (string, error) { +func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", err @@ -501,9 +501,9 @@ func preprocess(imp *openrtb.Imp, defaultDisplayManagerVer string) (string, erro if imp.Banner != nil { bannerCopy := *imp.Banner if appnexusExt.Position == "above" { - bannerCopy.Pos = openrtb.AdPositionAboveTheFold.Ptr() + bannerCopy.Pos = openrtb2.AdPositionAboveTheFold.Ptr() } else if appnexusExt.Position == "below" { - bannerCopy.Pos = openrtb.AdPositionBelowTheFold.Ptr() + bannerCopy.Pos = openrtb2.AdPositionBelowTheFold.Ptr() } // Fixes #307 @@ -550,7 +550,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -565,7 +565,7 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index d453a779854..076d93eacb6 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/cache/dummycache" @@ -20,7 +21,6 @@ import ( "fmt" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -65,16 +65,16 @@ func TestVideoSinglePod(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) result, err := a.MakeRequests(&req, &reqInfo) @@ -82,7 +82,7 @@ func TestVideoSinglePod(t *testing.T) { assert.Len(t, result, 1, "Only one request should be returned") var error error - var reqData *openrtb.BidRequest + var reqData *openrtb2.BidRequest error = json.Unmarshal(result[0].Body, &reqData) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -103,28 +103,28 @@ func TestVideoSinglePodManyImps(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_3", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_4", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_5", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_6", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_7", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_8", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_9", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_10", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_11", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_12", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_13", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_14", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_14", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -132,7 +132,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { assert.Len(t, res, 2, "Two requests should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -142,7 +142,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { adPodId1 := reqDataExt1.Appnexus.AdPodId - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -163,20 +163,20 @@ func TestVideoTwoPods(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_2", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -184,7 +184,7 @@ func TestVideoTwoPods(t *testing.T) { assert.Len(t, res, 2, "Two request should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -194,7 +194,7 @@ func TestVideoTwoPods(t *testing.T) { adPodId1 := reqDataExt1.Appnexus.AdPodId - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -215,32 +215,32 @@ func TestVideoTwoPodsManyImps(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) - - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_3", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_4", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_5", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_6", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_7", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_8", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_9", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_10", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_11", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_12", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_13", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_14", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) + + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_14", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -248,7 +248,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, res, 3, "Three requests should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -256,7 +256,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { error = json.Unmarshal(reqData1.Ext, &reqDataExt1) assert.NoError(t, error, "Response ext unmarshalling error should be nil") - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -264,7 +264,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { error = json.Unmarshal(reqData2.Ext, &reqDataExt2) assert.NoError(t, error, "Response ext unmarshalling error should be nil") - var reqData3 *openrtb.BidRequest + var reqData3 *openrtb2.BidRequest error = json.Unmarshal(res[2].Body, &reqData3) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -286,7 +286,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. type anTagInfo struct { code string @@ -325,7 +325,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -338,14 +338,14 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: breq.ID, BidID: "a-random-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "Buyer Member ID", - Bid: make([]openrtb.Bid, 0, 2), + Bid: make([]openrtb2.Bid, 0, 2), }, }, } @@ -415,11 +415,11 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) return } - if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb.AdPosition(1) { + if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) return } - if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb.AdPosition(3) { + if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) return } @@ -442,7 +442,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { } } - resBid := openrtb.Bid{ + resBid := openrtb2.Bid{ ID: "random-id", ImpID: imp.ID, Price: andata.tags[i].bid, @@ -451,7 +451,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { } if imp.Video != nil { - resBid.Attr = []openrtb.CreativeAttribute{openrtb.CreativeAttribute(6)} + resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} } resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) } @@ -568,7 +568,7 @@ func TestAppNexusLegacyBasicResponse(t *testing.T) { pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 600, diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 909a03a2bcf..f2aaaed8f88 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -10,19 +10,18 @@ import ( "net/http" "strings" + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/maputil" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" ) -var supportedBannerHeights = map[uint64]bool{ - 50: true, - 250: true, +var supportedBannerHeights = map[int64]struct{}{ + 50: {}, + 250: {}, } type FacebookAdapter struct { @@ -40,7 +39,7 @@ type facebookReqExt struct { AuthID string `json:"authentication_id"` } -func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (this *FacebookAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ Message: "No impressions provided", @@ -62,7 +61,7 @@ func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * return this.buildRequests(request) } -func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) { +func (this *FacebookAdapter) buildRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { // Documentation suggests bid request splitting by impression so that each // request only represents a single impression reqs := make([]*adapters.RequestData, 0, len(request.Imp)) @@ -77,7 +76,7 @@ func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adap // Make a copy of the request so that we don't change the original request which // is shared across multiple threads fbreq := *request - fbreq.Imp = []openrtb.Imp{imp} + fbreq.Imp = []openrtb2.Imp{imp} if err := this.modifyRequest(&fbreq); err != nil { errs = append(errs, err) @@ -109,14 +108,14 @@ func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adap // The authentication ID is a sha256 hmac hash encoded as a hex string, based on // the app secret and the ID of the bid request -func (this *FacebookAdapter) makeAuthID(req *openrtb.BidRequest) string { +func (this *FacebookAdapter) makeAuthID(req *openrtb2.BidRequest) string { h := hmac.New(sha256.New, []byte(this.appSecret)) h.Write([]byte(req.ID)) return hex.EncodeToString(h.Sum(nil)) } -func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { +func (this *FacebookAdapter) modifyRequest(out *openrtb2.BidRequest) error { if len(out.Imp) != 1 { panic("each bid request to facebook should only have a single impression") } @@ -146,7 +145,7 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { if out.App != nil { app := *out.App - app.Publisher = &openrtb.Publisher{ID: pubId} + app.Publisher = &openrtb2.Publisher{ID: pubId} out.App = &app } @@ -157,13 +156,8 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { return nil } -func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { - impType, ok := resolveImpType(out) - if !ok { - return &errortypes.BadInput{ - Message: fmt.Sprintf("imp #%s with invalid type", out.ID), - } - } +func (this *FacebookAdapter) modifyImp(out *openrtb2.Imp) error { + impType := resolveImpType(out) if out.Instl == 1 && impType != openrtb_ext.BidTypeBanner { return &errortypes.BadInput{ @@ -176,10 +170,9 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { out.Banner = &bannerCopy if out.Instl == 1 { - out.Banner.W = openrtb.Uint64Ptr(0) - out.Banner.H = openrtb.Uint64Ptr(0) + out.Banner.W = openrtb2.Int64Ptr(0) + out.Banner.H = openrtb2.Int64Ptr(0) out.Banner.Format = nil - return nil } @@ -204,15 +197,14 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { } } - /* This will get overwritten post-serialization */ - out.Banner.W = openrtb.Uint64Ptr(0) + out.Banner.W = openrtb2.Int64Ptr(-1) out.Banner.Format = nil } return nil } -func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (string, string, error) { +func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb2.Imp) (string, string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(out.Ext, &bidderExt); err != nil { return "", "", &errortypes.BadInput{ @@ -237,9 +229,9 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (str publisherID := fbExt.PublisherId // Support the legacy path with the caller was expected to pass in just placementId - // which was an underscore concantenated string with the publisherId and placementId. + // which was an underscore concatenated string with the publisherId and placementId. // The new path for callers is to pass in the placementId and publisherId independently - // and the below code will prefix the placementId that we pass to FAN with the publsiherId + // and the below code will prefix the placementId that we pass to FAN with the publisherId // so that we can abstract the implementation details from the caller toks := strings.Split(placementID, "_") if len(toks) == 1 { @@ -262,17 +254,17 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (str return placementID, publisherID, nil } -// XXX: This entire function is just a hack to get around mxmCherry 11.0.0 limitations, without -// having to fork the library and maintain our own branch -func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { - impType, ok := resolveImpType(imp) - if ok == false { - panic("processing an invalid impression") +// modifyImpCustom modifies the impression after it's marshalled to get around mxmCherry 14.0.0 limitations. +func modifyImpCustom(jsonData []byte, imp *openrtb2.Imp) ([]byte, error) { + impType := resolveImpType(imp) + + // we only need to modify video and native impressions + if impType != openrtb_ext.BidTypeVideo && impType != openrtb_ext.BidTypeNative { + return jsonData, nil } var jsonMap map[string]interface{} - err := json.Unmarshal(jsonData, &jsonMap) - if err != nil { + if err := json.Unmarshal(jsonData, &jsonMap); err != nil { return jsonData, err } @@ -286,28 +278,16 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } switch impType { - case openrtb_ext.BidTypeBanner: - // The current version of mxmCherry (11.0.0) represents banner.w as an unsigned - // integer, so setting a value of -1 is not possible which is why we have to do it - // post-serialization - isInterstitial := imp.Instl == 1 - if !isInterstitial { - if bannerMap, ok := maputil.ReadEmbeddedMap(impMap, "banner"); ok { - bannerMap["w"] = json.RawMessage("-1") - } else { - return jsonData, errors.New("unable to find imp[0].banner in json data") - } + case openrtb_ext.BidTypeVideo: + videoMap, ok := maputil.ReadEmbeddedMap(impMap, "video") + if !ok { + return jsonData, errors.New("unable to find imp[0].video in json data") } - case openrtb_ext.BidTypeVideo: // mxmCherry omits video.w/h if set to zero, so we need to force set those // fields to zero post-serialization for the time being - if videoMap, ok := maputil.ReadEmbeddedMap(impMap, "video"); ok { - videoMap["w"] = json.RawMessage("0") - videoMap["h"] = json.RawMessage("0") - } else { - return jsonData, errors.New("unable to find imp[0].video in json data") - } + videoMap["w"] = json.RawMessage("0") + videoMap["h"] = json.RawMessage("0") case openrtb_ext.BidTypeNative: nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") @@ -316,8 +296,8 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } // Set w/h to -1 for native impressions based on the facebook native spec. - // We have to set this post-serialization since the OpenRTB protocol doesn't - // actually support w/h in the native object + // We have to set this post-serialization since these fields are not included + // in the OpenRTB 2.5 spec. nativeMap["w"] = json.RawMessage("-1") nativeMap["h"] = json.RawMessage("-1") @@ -335,13 +315,11 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } } -func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - /* No bid response */ +func (this *FacebookAdapter) MakeBids(request *openrtb2.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } - /* Any other http status codes outside of 200 and 204 should be treated as errors */ if response.StatusCode != http.StatusOK { msg := response.Headers.Get("x-fb-an-errors") return nil, []error{&errortypes.BadInput{ @@ -349,7 +327,7 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -358,7 +336,9 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques var errs []error for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { + for i := range seatbid.Bid { + bid := seatbid.Bid[i] + if bid.AdM == "" { errs = append(errs, &errortypes.BadServerResponse{ Message: fmt.Sprintf("Bid %s missing 'adm'", bid.ID), @@ -394,38 +374,35 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques return out, errs } -func resolveBidType(bid *openrtb.Bid, req *openrtb.BidRequest) openrtb_ext.BidType { +func resolveBidType(bid *openrtb2.Bid, req *openrtb2.BidRequest) openrtb_ext.BidType { for _, imp := range req.Imp { if bid.ImpID == imp.ID { - if typ, ok := resolveImpType(&imp); ok { - return typ - } - - panic("Processing an invalid impression; cannot resolve impression type") + return resolveImpType(&imp) } } panic(fmt.Sprintf("Invalid bid imp ID %s does not match any imp IDs from the original bid request", bid.ImpID)) } -func resolveImpType(imp *openrtb.Imp) (openrtb_ext.BidType, bool) { +func resolveImpType(imp *openrtb2.Imp) openrtb_ext.BidType { if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, true + return openrtb_ext.BidTypeBanner } if imp.Video != nil { - return openrtb_ext.BidTypeVideo, true + return openrtb_ext.BidTypeVideo } if imp.Audio != nil { - return openrtb_ext.BidTypeAudio, true + return openrtb_ext.BidTypeAudio } if imp.Native != nil { - return openrtb_ext.BidTypeNative, true + return openrtb_ext.BidTypeNative } - return openrtb_ext.BidTypeBanner, false + // Required to satisfy compiler. Not reachable in practice due to validations performed in PBS-Core. + return openrtb_ext.BidTypeBanner } // Builder builds a new instance of Facebook's Audience Network adapter for the given bidder with the given config. diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go index dac7faaa4b3..b3cda3a1bdd 100644 --- a/adapters/avocet/avocet.go +++ b/adapters/avocet/avocet.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type AvocetAdapter struct { Endpoint string } -func (a *AvocetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AvocetAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, nil } @@ -50,7 +50,7 @@ type avocetBidExtension struct { DealPriority int `json:"deal_priority"` } -func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AvocetAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -68,7 +68,7 @@ func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var br openrtb.BidResponse + var br openrtb2.BidResponse err := json.Unmarshal(response.Body, &br) if err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -105,12 +105,12 @@ func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe } // getBidType returns the openrtb_ext.BidType for the provided bid. -func getBidType(bid openrtb.Bid, ext avocetBidExt) openrtb_ext.BidType { +func getBidType(bid openrtb2.Bid, ext avocetBidExt) openrtb_ext.BidType { if ext.Avocet.Duration != 0 { return openrtb_ext.BidTypeVideo } switch bid.API { - case openrtb.APIFrameworkVPAID10, openrtb.APIFrameworkVPAID20: + case openrtb2.APIFrameworkVPAID10, openrtb2.APIFrameworkVPAID20: return openrtb_ext.BidTypeVideo default: return openrtb_ext.BidTypeBanner diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go index e4a7c5f44fa..b256adc6f17 100644 --- a/adapters/avocet/avocet_test.go +++ b/adapters/avocet/avocet_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -30,7 +30,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { Endpoint string } type args struct { - request *openrtb.BidRequest + request *openrtb2.BidRequest reqInfo *adapters.ExtraRequestInfo } type reqData []*adapters.RequestData @@ -45,7 +45,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { name: "return nil if zero imps", fields: fields{Endpoint: "https://bid.avct.cloud"}, args: args{ - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, nil, }, want: nil, @@ -55,7 +55,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { name: "makes POST request with JSON content", fields: fields{Endpoint: "https://bid.avct.cloud"}, args: args{ - &openrtb.BidRequest{Imp: []openrtb.Imp{{}}}, + &openrtb2.BidRequest{Imp: []openrtb2.Imp{{}}}, nil, }, want: reqData{ @@ -100,7 +100,7 @@ func TestAvocetAdapter_MakeBids(t *testing.T) { Endpoint string } type args struct { - internalRequest *openrtb.BidRequest + internalRequest *openrtb2.BidRequest externalRequest *adapters.RequestData response *adapters.ResponseData } @@ -195,7 +195,7 @@ func TestAvocetAdapter_MakeBids(t *testing.T) { func Test_getBidType(t *testing.T) { type args struct { - bid openrtb.Bid + bid openrtb2.Bid ext avocetBidExt } tests := []struct { @@ -205,17 +205,17 @@ func Test_getBidType(t *testing.T) { }{ { name: "VPAID 1.0", - args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID10}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID10}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { name: "VPAID 2.0", - args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID20}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID20}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { name: "other", - args: args{openrtb.Bid{}, avocetBidExt{}}, + args: args{openrtb2.Bid{}, avocetBidExt{}}, want: openrtb_ext.BidTypeBanner, }, } @@ -228,7 +228,7 @@ func Test_getBidType(t *testing.T) { } } -var validBannerBid = openrtb.Bid{ +var validBannerBid = openrtb2.Bid{ AdM: "", ADomain: []string{"avocet.io"}, CID: "5b51e2d689654741306813a4", @@ -267,7 +267,7 @@ var validBannerBidResponseBody = []byte(`{ ] }`) -var validVideoBid = openrtb.Bid{ +var validVideoBid = openrtb2.Bid{ AdM: "Avocet", ADomain: []string{"avocet.io"}, CID: "5b51e2d689654741306813a4", diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 09dd9e93929..ea4ff2c83e5 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -51,9 +51,9 @@ type beachfrontRequests struct { // --------------------------------------------------- type beachfrontVideoRequest struct { - AppId string `json:"appId"` - VideoResponseType string `json:"videoResponseType"` - Request openrtb.BidRequest `json:"request"` + AppId string `json:"appId"` + VideoResponseType string `json:"videoResponseType"` + Request openrtb2.BidRequest `json:"request"` } // --------------------------------------------------- @@ -71,7 +71,7 @@ type beachfrontBannerRequest struct { IsMobile int8 `json:"isMobile"` UA string `json:"ua"` Dnt int8 `json:"dnt"` - User openrtb.User `json:"user"` + User openrtb2.User `json:"user"` AdapterName string `json:"adapterName"` AdapterVersion string `json:"adapterVersion"` IP string `json:"ip"` @@ -108,7 +108,7 @@ type beachfrontVideoBidExtension struct { Duration int `json:"duration"` } -func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *BeachfrontAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { beachfrontRequests, errs := preprocess(request) headers := http.Header{} @@ -198,9 +198,9 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a return reqs, errs } -func preprocess(request *openrtb.BidRequest) (beachfrontReqs beachfrontRequests, errs []error) { - var videoImps = make([]openrtb.Imp, 0) - var bannerImps = make([]openrtb.Imp, 0) +func preprocess(request *openrtb2.BidRequest) (beachfrontReqs beachfrontRequests, errs []error) { + var videoImps = make([]openrtb2.Imp, 0) + var bannerImps = make([]openrtb2.Imp, 0) for i := 0; i < len(request.Imp); i++ { if request.Imp[i].Banner != nil && request.Imp[i].Banner.Format != nil && @@ -267,7 +267,7 @@ func getAppId(ext openrtb_ext.ExtImpBeachfront, media openrtb_ext.BidType) (stri getBannerRequest, singular. A "Slot" is an "imp," and each Slot can have an AppId, so just one request to the beachfront banner endpoint gets all banner Imps. */ -func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []error) { +func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, []error) { var bfr beachfrontBannerRequest var errs = make([]error, 0, len(request.Imp)) @@ -301,8 +301,8 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e for j := 0; j < len(request.Imp[i].Banner.Format); j++ { slot.Sizes = append(slot.Sizes, beachfrontSize{ - H: request.Imp[i].Banner.Format[j].H, - W: request.Imp[i].Banner.Format[j].W, + H: uint64(request.Imp[i].Banner.Format[j].H), + W: uint64(request.Imp[i].Banner.Format[j].W), }) } @@ -327,7 +327,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e var t = fallBackDeviceType(request) - if t == openrtb.DeviceTypeMobileTablet { + if t == openrtb2.DeviceTypeMobileTablet { bfr.Page = request.App.Bundle if request.App.Domain == "" { bfr.Domain = getDomain(request.App.Domain) @@ -336,7 +336,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e } bfr.IsMobile = 1 - } else if t == openrtb.DeviceTypePersonalComputer { + } else if t == openrtb2.DeviceTypePersonalComputer { bfr.Page = request.Site.Page if request.Site.Domain == "" { bfr.Domain = getDomain(request.Site.Page) @@ -373,15 +373,15 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e return bfr, errs } -func fallBackDeviceType(request *openrtb.BidRequest) openrtb.DeviceType { +func fallBackDeviceType(request *openrtb2.BidRequest) openrtb2.DeviceType { if request.Site != nil { - return openrtb.DeviceTypePersonalComputer + return openrtb2.DeviceTypePersonalComputer } - return openrtb.DeviceTypeMobileTablet + return openrtb2.DeviceTypeMobileTablet } -func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []error) { +func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, []error) { var bfReqs = make([]beachfrontVideoRequest, len(request.Imp)) var errs = make([]error, 0, len(request.Imp)) var failedRequestIndicies = make([]int, 0) @@ -409,9 +409,9 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] bfReqs[i].Request = *request var secure int8 - var deviceCopy openrtb.Device + var deviceCopy openrtb2.Device if bfReqs[i].Request.Device == nil { - deviceCopy = openrtb.Device{} + deviceCopy = openrtb2.Device{} } else { deviceCopy = *bfReqs[i].Request.Device } @@ -473,7 +473,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } bfReqs[i].Request.Imp = nil - bfReqs[i].Request.Imp = make([]openrtb.Imp, 1) + bfReqs[i].Request.Imp = make([]openrtb2.Imp, 1) bfReqs[i].Request.Imp[0] = imp } @@ -488,7 +488,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] return bfReqs, errs } -func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -509,9 +509,9 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return nil, []error{fmt.Errorf("unexpected status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri)} } - var bids []openrtb.Bid + var bids []openrtb2.Bid var errs = make([]error, 0) - var xtrnal openrtb.BidRequest + var xtrnal openrtb2.BidRequest // For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will // only get the User struct and everything else will be nil @@ -560,10 +560,10 @@ func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) op return openrtb_ext.BidTypeBanner } -func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { +func postprocess(response *adapters.ResponseData, xtrnal openrtb2.BidRequest, uri string, id string) ([]openrtb2.Bid, []error) { var beachfrontResp []beachfrontResponseSlot - var openrtbResp openrtb.BidResponse + var openrtbResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &openrtbResp); err != nil || len(openrtbResp.SeatBid) == 0 { @@ -579,27 +579,27 @@ func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri return postprocessVideo(openrtbResp.SeatBid[0].Bid, xtrnal, uri, id) } -func postprocessBanner(beachfrontResp []beachfrontResponseSlot, id string) ([]openrtb.Bid, []error) { +func postprocessBanner(beachfrontResp []beachfrontResponseSlot, id string) ([]openrtb2.Bid, []error) { - var bids = make([]openrtb.Bid, len(beachfrontResp)) + var bids = make([]openrtb2.Bid, len(beachfrontResp)) var errs = make([]error, 0) for i := 0; i < len(beachfrontResp); i++ { - bids[i] = openrtb.Bid{ + bids[i] = openrtb2.Bid{ CrID: beachfrontResp[i].CrID, ImpID: beachfrontResp[i].Slot, Price: beachfrontResp[i].Price, ID: fmt.Sprintf("%sBanner", beachfrontResp[i].Slot), AdM: beachfrontResp[i].Adm, - H: beachfrontResp[i].H, - W: beachfrontResp[i].W, + H: int64(beachfrontResp[i].H), + W: int64(beachfrontResp[i].W), } } return bids, errs } -func postprocessVideo(bids []openrtb.Bid, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { +func postprocessVideo(bids []openrtb2.Bid, xtrnal openrtb2.BidRequest, uri string, id string) ([]openrtb2.Bid, []error) { var errs = make([]error, 0) @@ -633,7 +633,7 @@ func extractNurlVideoCrid(nurl string) string { return "" } -func getBeachfrontExtension(imp openrtb.Imp) (openrtb_ext.ExtImpBeachfront, error) { +func getBeachfrontExtension(imp openrtb2.Imp) (openrtb_ext.ExtImpBeachfront, error) { var err error var bidderExt adapters.ExtImpBidder var beachfrontExt openrtb_ext.ExtImpBeachfront diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go index 5f22bfa7742..8b5698750e6 100644 --- a/adapters/beintoo/beintoo.go +++ b/adapters/beintoo/beintoo.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type BeintooAdapter struct { endpoint string } -func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *BeintooAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors []error if len(request.Imp) == 0 { @@ -64,7 +64,7 @@ func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap }}, errors } -func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBeintoo, error) { +func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBeintoo, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -89,7 +89,7 @@ func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBeintoo, error) { return &beintooExt, nil } -func buildImpBanner(imp *openrtb.Imp) error { +func buildImpBanner(imp *openrtb2.Imp) error { imp.Ext = nil if imp.Banner == nil { @@ -118,7 +118,7 @@ func buildImpBanner(imp *openrtb.Imp) error { } // Add Beintoo required properties to Imp object -func addImpProps(imp *openrtb.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) { +func addImpProps(imp *openrtb2.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) { imp.TagID = BeintooExt.TagID imp.Secure = secure @@ -144,9 +144,9 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // Handle request errors and formatting to be sent to Beintoo -func preprocess(request *openrtb.BidRequest) []error { +func preprocess(request *openrtb2.BidRequest) []error { errors := make([]error, 0, len(request.Imp)) - resImps := make([]openrtb.Imp, 0, len(request.Imp)) + resImps := make([]openrtb2.Imp, 0, len(request.Imp)) secure := int8(0) if request.Site != nil && request.Site.Page != "" { @@ -178,7 +178,7 @@ func preprocess(request *openrtb.BidRequest) []error { } // MakeBids make the bids for the bid response. -func (a *BeintooAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *BeintooAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { // no bid response @@ -191,7 +191,7 @@ func (a *BeintooAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/beintoo/beintootest/supplemental/invalid-response-unmarshall-error.json b/adapters/beintoo/beintootest/supplemental/invalid-response-unmarshall-error.json index 5c5462c2990..6d587c2252b 100644 --- a/adapters/beintoo/beintootest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/beintoo/beintootest/supplemental/invalid-response-unmarshall-error.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "Unable to unpackage bid response\\. Error: json: cannot unmarshal string into Go struct field (Bid\\.seatbid\\.bid\\.w|Bid\\.w) of type uint64", + "value": "Unable to unpackage bid response\\. Error: json: cannot unmarshal string into Go struct field (Bid\\.seatbid\\.bid\\.w|Bid\\.w) of type int64", "comparison": "regex" } ] diff --git a/adapters/between/between.go b/adapters/between/between.go index f8106bdd113..f1be7db5fc2 100644 --- a/adapters/between/between.go +++ b/adapters/between/between.go @@ -8,7 +8,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -24,7 +24,7 @@ type BetweenAdapter struct { // If BidFloor of openrtb_ext.ExtImpBetween is zero, set it to defaultBidFloor value, see addImpProps const defaultBidfloor = 0.00001 -func (a *BetweenAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *BetweenAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors []error if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ @@ -70,7 +70,7 @@ func (a *BetweenAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap }}, errors } -func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBetween, error) { +func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBetween, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -103,7 +103,7 @@ func (a *BetweenAdapter) buildEndpointURL(e *openrtb_ext.ExtImpBetween) (string, return macros.ResolveMacros(a.EndpointTemplate, macros.EndpointTemplateParams{Host: e.Host, PublisherID: e.PublisherID}) } -func buildImpBanner(imp *openrtb.Imp) error { +func buildImpBanner(imp *openrtb2.Imp) error { if imp.Banner == nil { return &errortypes.BadInput{ Message: fmt.Sprintf("Request needs to include a Banner object"), @@ -127,7 +127,7 @@ func buildImpBanner(imp *openrtb.Imp) error { } // Add Between required properties to Imp object -func addImpProps(imp *openrtb.Imp, secure *int8, betweenExt *openrtb_ext.ExtImpBetween) { +func addImpProps(imp *openrtb2.Imp, secure *int8, betweenExt *openrtb_ext.ExtImpBetween) { imp.Secure = secure if betweenExt.BidFloor <= 0 { imp.BidFloor = defaultBidfloor @@ -147,9 +147,9 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // Handle request errors and formatting to be sent to Between -func preprocess(request *openrtb.BidRequest) (*openrtb_ext.ExtImpBetween, []error) { +func preprocess(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpBetween, []error) { errors := make([]error, 0, len(request.Imp)) - resImps := make([]openrtb.Imp, 0, len(request.Imp)) + resImps := make([]openrtb2.Imp, 0, len(request.Imp)) secure := int8(0) if request.Site != nil && request.Site.Page != "" { @@ -181,7 +181,7 @@ func preprocess(request *openrtb.BidRequest) (*openrtb_ext.ExtImpBetween, []erro return betweenExt, errors } -func (a *BetweenAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *BetweenAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { // no bid response @@ -193,7 +193,7 @@ func (a *BetweenAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode), }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Unable to unpackage bid response. Error %s", err.Error()), diff --git a/adapters/between/betweentest/supplemental/bad-response-body.json b/adapters/between/betweentest/supplemental/bad-response-body.json index 1712010a86b..fbd08206bd8 100644 --- a/adapters/between/betweentest/supplemental/bad-response-body.json +++ b/adapters/between/betweentest/supplemental/bad-response-body.json @@ -70,7 +70,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "Unable to unpackage bid response. Error json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Unable to unpackage bid response. Error json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/bidder.go b/adapters/bidder.go index 9800a49c9cb..a389299f888 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -5,7 +5,7 @@ import ( "encoding/json" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -26,7 +26,7 @@ type Bidder interface { // "subpar" in some way. For example: the request contained ad types which this bidder doesn't support. // // If the error is caused by bad user input, return an errortypes.BadInput. - MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) + MakeRequests(request *openrtb2.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) // MakeBids unpacks the server's response into Bids. // @@ -37,7 +37,7 @@ type Bidder interface { // // If the error was caused by bad user input, return a errortypes.BadInput. // If the error was caused by a bad server response, return a errortypes.BadServerResponse - MakeBids(internalRequest *openrtb.BidRequest, externalRequest *RequestData, response *ResponseData) (*BidderResponse, []error) + MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *RequestData, response *ResponseData) (*BidderResponse, []error) } // TimeoutBidder is used to identify bidders that support timeout notifications. @@ -84,7 +84,7 @@ func NewBidderResponse() *BidderResponse { return NewBidderResponseWithBidsCapacity(0) } -// TypedBid packages the openrtb.Bid with any bidder-specific information that PBS needs to populate an +// TypedBid packages the openrtb2.Bid with any bidder-specific information that PBS needs to populate an // openrtb_ext.ExtBidPrebid. // // TypedBid.Bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response. @@ -92,7 +92,7 @@ func NewBidderResponse() *BidderResponse { // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response. // TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. type TypedBid struct { - Bid *openrtb.Bid + Bid *openrtb2.Bid BidType openrtb_ext.BidType BidVideo *openrtb_ext.ExtBidPrebidVideo DealPriority int diff --git a/adapters/brightroll/brightroll.go b/adapters/brightroll/brightroll.go index 0e3fcb0669c..fe770b07d16 100644 --- a/adapters/brightroll/brightroll.go +++ b/adapters/brightroll/brightroll.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,7 +30,7 @@ type Account struct { BidFloor float64 `json:"bidfloor"` } -func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { request := *requestIn errs := make([]error, 0, len(request.Imp)) @@ -162,7 +162,7 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo }}, errors } -func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -180,7 +180,7 @@ func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %d. ", err), @@ -199,10 +199,10 @@ func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, nil } -func getBlockedCreativetypes(attr []int8) []openrtb.CreativeAttribute { - var creativeAttr []openrtb.CreativeAttribute +func getBlockedCreativetypes(attr []int8) []openrtb2.CreativeAttribute { + var creativeAttr []openrtb2.CreativeAttribute for i := 0; i < len(attr); i++ { - creativeAttr = append(creativeAttr, openrtb.CreativeAttribute(attr[i])) + creativeAttr = append(creativeAttr, openrtb2.CreativeAttribute(attr[i])) } return creativeAttr } @@ -215,7 +215,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // getMediaTypeForImp figures out which media type this bid is for. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner //default type for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go index 4a5360ce122..801b338478a 100644 --- a/adapters/colossus/colossus.go +++ b/adapters/colossus/colossus.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests create bid request for colossus demand -func (a *ColossusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ColossusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var err error var tagID string @@ -35,7 +35,7 @@ func (a *ColossusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") if err != nil { @@ -54,7 +54,7 @@ func (a *ColossusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada return adapterRequests, errs } -func (a *ColossusAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *ColossusAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -77,7 +77,7 @@ func (a *ColossusAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Re } // MakeBids makes the bids -func (a *ColossusAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ColossusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -96,7 +96,7 @@ func (a *ColossusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -121,7 +121,7 @@ func (a *ColossusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/colossus/colossustest/supplemental/bad_response.json b/adapters/colossus/colossustest/supplemental/bad_response.json index c69b00c8e6e..62b862b181e 100644 --- a/adapters/colossus/colossustest/supplemental/bad_response.json +++ b/adapters/colossus/colossustest/supplemental/bad_response.json @@ -78,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 024dabcee98..9192a520a05 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,7 +30,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *ConnectAdAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ConnectAdAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -73,7 +73,7 @@ func (a *ConnectAdAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad }}, errs } -func (a *ConnectAdAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ConnectAdAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { if httpRes.StatusCode == http.StatusNoContent { return nil, nil @@ -85,7 +85,7 @@ func (a *ConnectAdAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -107,10 +107,10 @@ func (a *ConnectAdAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters return bidResponse, nil } -func preprocess(request *openrtb.BidRequest) []error { +func preprocess(request *openrtb2.BidRequest) []error { impsCount := len(request.Imp) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) secure := int8(0) if request.Site != nil && request.Site.Page != "" { @@ -141,7 +141,7 @@ func preprocess(request *openrtb.BidRequest) []error { return errors } -func addImpInfo(imp *openrtb.Imp, secure *int8, cadExt *openrtb_ext.ExtImpConnectAd) { +func addImpInfo(imp *openrtb2.Imp, secure *int8, cadExt *openrtb_ext.ExtImpConnectAd) { imp.TagID = strconv.Itoa(cadExt.SiteID) imp.Secure = secure @@ -159,7 +159,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpConnectAd, error) { +func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpConnectAd, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -183,7 +183,7 @@ func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpConnectAd, error) { return &cadExt, nil } -func buildImpBanner(imp *openrtb.Imp) error { +func buildImpBanner(imp *openrtb2.Imp) error { imp.Ext = nil if imp.Banner == nil { diff --git a/adapters/connectad/connectadtest/supplemental/badresponse.json b/adapters/connectad/connectadtest/supplemental/badresponse.json index de56d8b4c91..6c3bdaa4985 100644 --- a/adapters/connectad/connectadtest/supplemental/badresponse.json +++ b/adapters/connectad/connectadtest/supplemental/badresponse.json @@ -83,7 +83,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "Unable to unpackage bid response. Error: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Unable to unpackage bid response. Error: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/consumable/adtypes.go b/adapters/consumable/adtypes.go index 5eb3d3b369e..20b89bdfe7f 100644 --- a/adapters/consumable/adtypes.go +++ b/adapters/consumable/adtypes.go @@ -1,16 +1,17 @@ package consumable import ( - "github.com/mxmCherry/openrtb" "strconv" + + "github.com/mxmCherry/openrtb/v14/openrtb2" ) /* Turn array of openrtb formats into consumable's code*/ -func getSizeCodes(Formats []openrtb.Format) []int { +func getSizeCodes(Formats []openrtb2.Format) []int { codes := make([]int, 0) for _, format := range Formats { - str := strconv.FormatUint(format.W, 10) + "x" + strconv.FormatUint(format.H, 10) + str := strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10) if code, ok := sizeMap[str]; ok { codes = append(codes, code) } diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index 15511c0ebb7..d81bc20b0d3 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -83,7 +83,7 @@ type pricing struct { ClearPrice *float64 `json:"clearPrice"` } -func (a *ConsumableAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { headers := http.Header{ "Content-Type": {"application/json"}, "Accept": {"application/json"}, @@ -211,7 +211,7 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) */ func (a *ConsumableAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -245,13 +245,13 @@ func (a *ConsumableAdapter) MakeBids( for impID, decision := range serverResponse.Decisions { if decision.Pricing != nil && decision.Pricing.ClearPrice != nil { - bid := openrtb.Bid{} + bid := openrtb2.Bid{} bid.ID = internalRequest.ID bid.ImpID = impID bid.Price = *decision.Pricing.ClearPrice bid.AdM = retrieveAd(decision) - bid.W = decision.Width - bid.H = decision.Height + bid.W = int64(decision.Width) + bid.H = int64(decision.Height) bid.CrID = strconv.FormatInt(decision.AdID, 10) bid.Exp = 30 // TODO: Check this is intention of TTL @@ -273,7 +273,7 @@ func (a *ConsumableAdapter) MakeBids( return bidderResponse, errors } -func extractExtensions(impression openrtb.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) { +func extractExtensions(impression openrtb2.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(impression.Ext, &bidderExt); err != nil { return nil, nil, []error{&errortypes.BadInput{ diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go index 4672ee156b4..e29fb7c019b 100644 --- a/adapters/conversant/cnvr_legacy.go +++ b/adapters/conversant/cnvr_legacy.go @@ -8,7 +8,7 @@ import ( "io/ioutil" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/pbs" @@ -54,7 +54,7 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, // Create a map of impression objects for both request creation // and response parsing. - impMap := make(map[string]*openrtb.Imp, len(cnvrReq.Imp)) + impMap := make(map[string]*openrtb2.Imp, len(cnvrReq.Imp)) for idx := range cnvrReq.Imp { impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] } @@ -98,9 +98,9 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, imp.BidFloor = params.BidFloor imp.TagID = params.TagID - var position *openrtb.AdPosition + var position *openrtb2.AdPosition if params.Position != nil { - position = openrtb.AdPosition(*params.Position).Ptr() + position = openrtb2.AdPosition(*params.Position).Ptr() } if imp.Banner != nil { @@ -109,9 +109,9 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, imp.Video.Pos = position if len(params.API) > 0 { - imp.Video.API = make([]openrtb.APIFramework, 0, len(params.API)) + imp.Video.API = make([]openrtb2.APIFramework, 0, len(params.API)) for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) + imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) } } @@ -120,9 +120,9 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, // but are overridden if the custom params object also contains them. if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb.Protocol, 0, len(params.Protocols)) + imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(params.Protocols)) for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) } } @@ -164,7 +164,7 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, } if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb.Device{} + cnvrReq.Device = &openrtb2.Device{} } // Convert request to json to be sent over http @@ -217,7 +217,7 @@ func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, debug.ResponseBody = string(body) } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go index 712f85f4404..ef8a780c746 100644 --- a/adapters/conversant/cnvr_legacy_test.go +++ b/adapters/conversant/cnvr_legacy_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" @@ -497,12 +497,12 @@ func TestConversantVideoRequestWithParams(t *testing.T) { assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb.APIFramework(2), "Request video api 2") + assertEqual(t, imp.Video.API[0], openrtb2.APIFramework(1), "Request video api 1") + assertEqual(t, imp.Video.API[1], openrtb2.APIFramework(2), "Request video api 2") } // Test video request with parameters in the video object @@ -557,8 +557,8 @@ func TestConversantVideoRequestWithParams2(t *testing.T) { assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") } @@ -628,7 +628,7 @@ func CreateRequest(params ...string) *pbs.PBSRequest { for i := 0; i < num; i++ { req.AdUnits[i] = pbs.AdUnit{ Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, @@ -672,7 +672,7 @@ func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBS // Convert a request to an app request by adding required properties func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb.App) + app := new(openrtb2.App) err := json.Unmarshal([]byte(appParams), &app) if err == nil { req.App = app @@ -728,8 +728,8 @@ func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { // Helper to create a test http server that receives and generate openrtb requests and responses -func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { - var lastBidRequest openrtb.BidRequest +func CreateServer(prices ...float64) (*httptest.Server, *openrtb2.BidRequest) { + var lastBidRequest openrtb2.BidRequest server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -738,10 +738,10 @@ func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { return } - var bidReq openrtb.BidRequest + var bidReq openrtb2.BidRequest var price float64 - var bids []openrtb.Bid - var bid openrtb.Bid + var bids []openrtb2.Bid + var bid openrtb2.Bid err = json.Unmarshal(body, &bidReq) if err != nil { @@ -758,7 +758,7 @@ func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { } if price > 0 { - bid = openrtb.Bid{ + bid = openrtb2.Bid{ ID: imp.ID, ImpID: imp.ID, Price: price, @@ -775,7 +775,7 @@ func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { bid.H = imp.Video.H } } else { - bid = openrtb.Bid{ + bid = openrtb2.Bid{ ID: imp.ID, ImpID: imp.ID, Price: 0, @@ -788,9 +788,9 @@ func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { if len(bids) == 0 { w.WriteHeader(http.StatusNoContent) } else { - js, _ := json.Marshal(openrtb.BidResponse{ + js, _ := json.Marshal(openrtb2.BidResponse{ ID: bidReq.ID, - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Bid: bids, }, @@ -808,9 +808,9 @@ func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { // Helper to remove impressions with $0 bids -func FilterZeroPrices(prices []float64, imps []openrtb.Imp) ([]float64, []openrtb.Imp) { +func FilterZeroPrices(prices []float64, imps []openrtb2.Imp) ([]float64, []openrtb2.Imp) { prices2 := make([]float64, 0) - imps2 := make([]openrtb.Imp, 0) + imps2 := make([]openrtb2.Imp, 0) for i := range prices { if prices[i] > 0 { diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index 248aa200d8e..d742033437a 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type ConversantAdapter struct { URI string } -func (c ConversantAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (c ConversantAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { for i := 0; i < len(request.Imp); i++ { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(request.Imp[i].Ext, &bidderExt); err != nil { @@ -71,7 +71,7 @@ func (c ConversantAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad }}, nil } -func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { +func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant) { imp.DisplayManager = "prebid-s2s" imp.DisplayManagerVer = "2.0.0" imp.BidFloor = cnvrExt.BidFloor @@ -82,9 +82,9 @@ func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { imp.Secure = cnvrExt.Secure } - var position *openrtb.AdPosition + var position *openrtb2.AdPosition if cnvrExt.Position != nil { - position = openrtb.AdPosition(*cnvrExt.Position).Ptr() + position = openrtb2.AdPosition(*cnvrExt.Position).Ptr() } if imp.Banner != nil { tmpBanner := *imp.Banner @@ -97,9 +97,9 @@ func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { imp.Video.Pos = position if len(cnvrExt.API) > 0 { - imp.Video.API = make([]openrtb.APIFramework, 0, len(cnvrExt.API)) + imp.Video.API = make([]openrtb2.APIFramework, 0, len(cnvrExt.API)) for _, api := range cnvrExt.API { - imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) + imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) } } @@ -108,9 +108,9 @@ func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { // but are overridden if the custom params object also contains them. if len(cnvrExt.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb.Protocol, 0, len(cnvrExt.Protocols)) + imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(cnvrExt.Protocols)) for _, protocol := range cnvrExt.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) } } @@ -125,7 +125,7 @@ func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { } } -func (c ConversantAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (c ConversantAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil // no bid response } @@ -136,7 +136,7 @@ func (c ConversantAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var resp openrtb.BidResponse + var resp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &resp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %d. ", err), @@ -160,7 +160,7 @@ func (c ConversantAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa return bidResponse, nil } -func getBidType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go index f3fbdf70516..b3100222e98 100644 --- a/adapters/cpmstar/cpmstar.go +++ b/adapters/cpmstar/cpmstar.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type Adapter struct { endpoint string } -func (a *Adapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *Adapter) MakeRequests(request *openrtb2.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -36,7 +36,7 @@ func (a *Adapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.Ext return adapterRequests, errs } -func (a *Adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *Adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { var err error jsonBody, err := json.Marshal(request) @@ -55,7 +55,7 @@ func (a *Adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestDat }, nil } -func preprocess(request *openrtb.BidRequest) error { +func preprocess(request *openrtb2.BidRequest) error { if len(request.Imp) == 0 { return &errortypes.BadInput{ Message: "No Imps in Bid Request", @@ -88,7 +88,7 @@ func preprocess(request *openrtb.BidRequest) error { return nil } -func validateImp(imp *openrtb.Imp) error { +func validateImp(imp *openrtb2.Imp) error { if imp.Banner == nil && imp.Video == nil { return &errortypes.BadInput{ Message: "Only Banner and Video bid-types are supported at this time", @@ -98,7 +98,7 @@ func validateImp(imp *openrtb.Imp) error { } // MakeBids based on cpmstar server response -func (a *Adapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *Adapter) MakeBids(bidRequest *openrtb2.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil } @@ -109,7 +109,7 @@ func (a *Adapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.Requ }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json index e20acefe2c3..4350b2dc714 100644 --- a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json @@ -59,7 +59,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type int64", "comparison": "regex" } ] diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index 56ac8f681d7..28b9c36360e 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type DatablocksAdapter struct { EndpointTemplate template.Template } -func (a *DatablocksAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *DatablocksAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) headers := http.Header{ @@ -69,7 +69,7 @@ func (a *DatablocksAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) */ func (a *DatablocksAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -84,7 +84,7 @@ func (a *DatablocksAdapter) MakeBids( }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -106,9 +106,9 @@ func (a *DatablocksAdapter) MakeBids( return bidResponse, nil } -func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp, error) { +func splitImpressions(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpDatablocks][]openrtb2.Imp, error) { - var m = make(map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp) + var m = make(map[openrtb_ext.ExtImpDatablocks][]openrtb2.Imp) for _, imp := range imps { bidderParams, err := getBidderParams(&imp) @@ -120,14 +120,14 @@ func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpDatablocks][]op if ok { m[*bidderParams] = append(v, imp) } else { - m[*bidderParams] = []openrtb.Imp{imp} + m[*bidderParams] = []openrtb2.Imp{imp} } } return m, nil } -func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpDatablocks, error) { +func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpDatablocks, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -156,7 +156,7 @@ func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpDatablocks, error) { return &datablocksExt, nil } -func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaType(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner diff --git a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json index c7ecf3c0eaa..7c4801e1685 100644 --- a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json +++ b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json @@ -81,7 +81,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go index 5719bf1e4b3..ac60a4042ae 100644 --- a/adapters/decenterads/decenterads.go +++ b/adapters/decenterads/decenterads.go @@ -7,7 +7,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type adapter struct { endpoint string } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -40,7 +40,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraReq } impression.Ext = bidderExt - request.Imp = []openrtb.Imp{impression} + request.Imp = []openrtb2.Imp{impression} body, err := json.Marshal(request) if err != nil { errs = append(errs, err) @@ -58,7 +58,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraReq return result, errs } -func (a *adapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error switch responseData.StatusCode { @@ -76,7 +76,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse err := json.Unmarshal(responseData.Body, &bidResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -98,7 +98,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, return response, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/decenterads/decenteradstest/supplemental/bad_response.json b/adapters/decenterads/decenteradstest/supplemental/bad_response.json index 9d9d977b14a..487d6adf413 100644 --- a/adapters/decenterads/decenteradstest/supplemental/bad_response.json +++ b/adapters/decenterads/decenteradstest/supplemental/bad_response.json @@ -76,7 +76,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 1ddaa1563c9..17168ae05f8 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -33,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } //MakeRequests which creates request object for Deepintent DSP -func (d *DeepintentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (d *DeepintentAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var deepintentExt openrtb_ext.ExtImpDeepintent var err error @@ -42,7 +42,7 @@ func (d *DeepintentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} var bidderExt adapters.ExtImpBidder if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { @@ -76,7 +76,7 @@ func (d *DeepintentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a } // MakeBids makes the bids -func (d *DeepintentAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (d *DeepintentAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -89,7 +89,7 @@ func (d *DeepintentAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -114,11 +114,11 @@ func (d *DeepintentAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, errs } -func (d *DeepintentAdapter) preprocess(request openrtb.BidRequest) (*adapters.RequestData, []error) { +func (d *DeepintentAdapter) preprocess(request openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error impsCount := len(request.Imp) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) for _, imp := range request.Imp { @@ -150,7 +150,7 @@ func (d *DeepintentAdapter) preprocess(request openrtb.BidRequest) (*adapters.Re }, errs } -func buildImpBanner(imp *openrtb.Imp) error { +func buildImpBanner(imp *openrtb2.Imp) error { if imp.Banner == nil { return &errortypes.BadInput{ @@ -176,7 +176,7 @@ func buildImpBanner(imp *openrtb.Imp) error { return nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/deepintent/deepintenttest/supplemental/bad_response.json b/adapters/deepintent/deepintenttest/supplemental/bad_response.json index c03531b6232..b2c4c210df6 100644 --- a/adapters/deepintent/deepintenttest/supplemental/bad_response.json +++ b/adapters/deepintent/deepintenttest/supplemental/bad_response.json @@ -84,7 +84,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index c66c6bf38fa..79e1d751712 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -55,15 +55,15 @@ func UserSellerOrPubId(str1, str2 string) string { return str2 } -func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { - var imps []openrtb.Imp +func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { + var imps []openrtb2.Imp var rootExtInfo dmxExt var publisherId string var sellerId string var userExt openrtb_ext.ExtUser var anyHasId = false - var reqCopy openrtb.BidRequest = *request - var dmxReq *openrtb.BidRequest = &reqCopy + var reqCopy openrtb2.BidRequest = *request + var dmxReq *openrtb2.BidRequest = &reqCopy var dmxRawPubId dmxPubExt if request.User == nil { @@ -121,7 +121,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte } dmxReq.Site.Publisher.Ext = ext } else { - dmxReq.Site.Publisher = &openrtb.Publisher{ID: publisherId} + dmxReq.Site.Publisher = &openrtb2.Publisher{ID: publisherId} } } else { dmxReq.Site = nil @@ -152,9 +152,9 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte } for _, inst := range dmxReq.Imp { - var banner *openrtb.Banner - var video *openrtb.Video - var ins openrtb.Imp + var banner *openrtb2.Banner + var video *openrtb2.Video + var ins openrtb2.Imp var params dmxExt const intVal int8 = 1 source := (*json.RawMessage)(&inst.Ext) @@ -207,7 +207,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte return } -func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *DmxAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if http.StatusNoContent == response.StatusCode { @@ -226,7 +226,7 @@ func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -255,8 +255,8 @@ func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest } -func fetchParams(params dmxExt, inst openrtb.Imp, ins openrtb.Imp, imps []openrtb.Imp, banner *openrtb.Banner, video *openrtb.Video, intVal int8) []openrtb.Imp { - var tempimp openrtb.Imp +func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []openrtb2.Imp, banner *openrtb2.Banner, video *openrtb2.Video, intVal int8) []openrtb2.Imp { + var tempimp openrtb2.Imp tempimp = inst if params.Bidder.Bidfloor != 0 { tempimp.BidFloor = params.Bidder.Bidfloor @@ -292,7 +292,7 @@ func addParams(str string) string { return "" } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { @@ -309,7 +309,7 @@ func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, } } -func videoImpInsertion(bid *openrtb.Bid) string { +func videoImpInsertion(bid *openrtb2.Bid) string { adm := bid.AdM nurl := bid.NURL search := "" diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 80498de6e04..4288f2fa940 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -20,17 +20,17 @@ var ( func TestFetchParams(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) - var arrImp []openrtb.Imp + var width, height int64 = int64(w), int64(h) + var arrImp []openrtb2.Imp var imps = fetchParams( dmxExt{Bidder: dmxParams{ TagId: "222", PublisherId: "5555", }}, - openrtb.Imp{ID: "32"}, - openrtb.Imp{ID: "32"}, + openrtb2.Imp{ID: "32"}, + openrtb2.Imp{ID: "32"}, arrImp, - &openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{ + &openrtb2.Banner{W: &width, H: &height, Format: []openrtb2.Format{ {W: 300, H: 250}, }}, nil, @@ -40,10 +40,10 @@ func TestFetchParams(t *testing.T) { DmxId: "222", MemberId: "5555", }}, - openrtb.Imp{ID: "32"}, - openrtb.Imp{ID: "32"}, + openrtb2.Imp{ID: "32"}, + openrtb2.Imp{ID: "32"}, arrImp, - &openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{ + &openrtb2.Banner{W: &width, H: &height, Format: []openrtb2.Format{ {W: 300, H: 250}, }}, nil, @@ -70,7 +70,7 @@ func TestJsonSamples(t *testing.T) { func TestMakeRequestsOtherPlacement(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -79,22 +79,22 @@ func TestMakeRequestsOtherPlacement(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - User: &openrtb.User{ID: "bscakucbkasucbkasunscancasuin"}, - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + User: &openrtb2.User{ID: "bscakucbkasucbkasunscancasuin"}, + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, @@ -115,7 +115,7 @@ func TestMakeRequestsOtherPlacement(t *testing.T) { func TestMakeRequestsInvalid(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -124,21 +124,21 @@ func TestMakeRequestsInvalid(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, @@ -159,7 +159,7 @@ func TestMakeRequestsInvalid(t *testing.T) { func TestMakeRequestNoSite(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -168,20 +168,20 @@ func TestMakeRequestNoSite(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}}, + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + App: &openrtb2.App{ID: "cansanuabnua", Publisher: &openrtb2.Publisher{ID: "whatever"}}, ID: "1234", } @@ -190,7 +190,7 @@ func TestMakeRequestNoSite(t *testing.T) { if len(actualAdapterRequests) != 1 { t.Errorf("openrtb type should be an Array when it's an App") } - var the_body openrtb.BidRequest + var the_body openrtb2.BidRequest if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { t.Errorf("failed to read bid request") } @@ -207,7 +207,7 @@ func TestMakeRequestNoSite(t *testing.T) { func TestMakeRequestsApp(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -216,25 +216,25 @@ func TestMakeRequestsApp(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}}, + App: &openrtb2.App{ID: "cansanuabnua", Publisher: &openrtb2.Publisher{ID: "whatever"}}, ID: "1234", } @@ -243,7 +243,7 @@ func TestMakeRequestsApp(t *testing.T) { if len(actualAdapterRequests) != 1 { t.Errorf("openrtb type should be an Array when it's an App") } - var the_body openrtb.BidRequest + var the_body openrtb2.BidRequest if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { t.Errorf("failed to read bid request") } @@ -257,7 +257,7 @@ func TestMakeRequestsApp(t *testing.T) { func TestMakeRequestsNoUser(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -266,21 +266,21 @@ func TestMakeRequestsNoUser(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, @@ -296,10 +296,9 @@ func TestMakeRequestsNoUser(t *testing.T) { } func TestMakeRequests(t *testing.T) { - //server := httptest.NewServer(http.HandlerFunc(DummyDmxServer)) var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -308,45 +307,45 @@ func TestMakeRequests(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "imp2", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - imp3 := openrtb.Imp{ + imp3 := openrtb2.Imp{ ID: "imp3", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1, imp2, imp3}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1, imp2, imp3}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{ID: "districtmID"}, + User: &openrtb2.User{ID: "districtmID"}, ID: "1234", } @@ -355,7 +354,7 @@ func TestMakeRequests(t *testing.T) { if len(actualAdapterRequests) != 1 { t.Errorf("should have 1 request") } - var the_body openrtb.BidRequest + var the_body openrtb2.BidRequest if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { t.Errorf("failed to read bid request") } @@ -369,7 +368,7 @@ func TestMakeRequests(t *testing.T) { func TestMakeBidVideo(t *testing.T) { var w, h int = 640, 480 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -378,23 +377,23 @@ func TestMakeBidVideo(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: width, H: height, MIMEs: []string{"video/mp4"}, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{ID: "districtmID"}, + User: &openrtb2.User{ID: "districtmID"}, ID: "1234", } @@ -403,7 +402,7 @@ func TestMakeBidVideo(t *testing.T) { if len(actualAdapterRequests) != 1 { t.Errorf("should have 1 request") } - var the_body openrtb.BidRequest + var the_body openrtb2.BidRequest if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { t.Errorf("failed to read bid request") } @@ -416,7 +415,7 @@ func TestMakeBidVideo(t *testing.T) { func TestMakeBidsNoContent(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -425,25 +424,25 @@ func TestMakeBidsNoContent(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{ID: "districtmID"}, + User: &openrtb2.User{ID: "districtmID"}, ID: "1234", } @@ -554,7 +553,7 @@ func TestMakeBidsNoContent(t *testing.T) { func TestUserExtEmptyObject(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -563,25 +562,25 @@ func TestUserExtEmptyObject(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1, imp1, imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1, imp1, imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{Ext: json.RawMessage(`{}`)}, + User: &openrtb2.User{Ext: json.RawMessage(`{}`)}, ID: "1234", } @@ -593,7 +592,7 @@ func TestUserExtEmptyObject(t *testing.T) { func TestUserEidsOnly(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -602,25 +601,25 @@ func TestUserEidsOnly(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1, imp1, imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1, imp1, imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{Ext: json.RawMessage(`{"eids": [{ + User: &openrtb2.User{Ext: json.RawMessage(`{"eids": [{ "source": "adserver.org", "uids": [{ "id": "111111111111", @@ -647,7 +646,7 @@ func TestUserEidsOnly(t *testing.T) { func TestUserDigitrustOnly(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -656,25 +655,25 @@ func TestUserDigitrustOnly(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1, imp1, imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1, imp1, imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{Ext: json.RawMessage(`{ + User: &openrtb2.User{Ext: json.RawMessage(`{ "digitrust": { "id": "11111111111", "keyv": 4 @@ -691,7 +690,7 @@ func TestUserDigitrustOnly(t *testing.T) { func TestUsersEids(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ Endpoint: "https://dmx.districtm.io/b/v2"}) @@ -700,25 +699,25 @@ func TestUsersEids(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "imp1", Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} - inputRequest := openrtb.BidRequest{ - Imp: []openrtb.Imp{imp1, imp1, imp1}, - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + inputRequest := openrtb2.BidRequest{ + Imp: []openrtb2.Imp{imp1, imp1, imp1}, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "10007", }, }, - User: &openrtb.User{ID: "districtmID", Ext: json.RawMessage(`{"eids": [{ + User: &openrtb2.User{ID: "districtmID", Ext: json.RawMessage(`{"eids": [{ "source": "adserver.org", "uids": [{ "id": "111111111111", @@ -780,7 +779,7 @@ func TestUsersEids(t *testing.T) { if len(actualAdapterRequests) != 1 { t.Errorf("should have 1 request") } - var the_body openrtb.BidRequest + var the_body openrtb2.BidRequest if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil { t.Errorf("failed to read bid request") } @@ -790,8 +789,8 @@ func TestUsersEids(t *testing.T) { } } func TestVideoImpInsertion(t *testing.T) { - var bidResp openrtb.BidResponse - var bid openrtb.Bid + var bidResp openrtb2.BidResponse + var bid openrtb2.Bid payload := []byte(`{ "id": "some-request-id", "seatbid": [ @@ -853,7 +852,7 @@ func TestVideoImpInsertion(t *testing.T) { if err != nil { t.Errorf("Payload is invalid") } - bid = openrtb.Bid(bidResp.SeatBid[0].Bid[0]) + bid = openrtb2.Bid(bidResp.SeatBid[0].Bid[0]) data := videoImpInsertion(&bid) find := strings.Index(data, "demo.arripiblik.com") if find == -1 { diff --git a/adapters/emx_digital/emx_digital.go b/adapters/emx_digital/emx_digital.go index e87f0681075..98a8b6eb943 100644 --- a/adapters/emx_digital/emx_digital.go +++ b/adapters/emx_digital/emx_digital.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func buildEndpoint(endpoint string, testing bool, timeout int64) string { return endpoint + "?t=" + strconv.FormatInt(timeout, 10) + "&ts=" + strconv.FormatInt(time.Now().Unix(), 10) + "&src=pbserver" } -func (a *EmxDigitalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *EmxDigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error if len(request.Imp) == 0 { @@ -80,7 +80,7 @@ func (a *EmxDigitalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a }}, errs } -func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpEmxDigital, error) { +func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpEmxDigital, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -111,7 +111,7 @@ func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpEmxDigital, error) { return &emxExt, nil } -func buildImpBanner(imp *openrtb.Imp) error { +func buildImpBanner(imp *openrtb2.Imp) error { if imp.Banner == nil { return &errortypes.BadInput{ @@ -138,7 +138,7 @@ func buildImpBanner(imp *openrtb.Imp) error { return nil } -func buildImpVideo(imp *openrtb.Imp) error { +func buildImpVideo(imp *openrtb2.Imp) error { if len(imp.Video.MIMEs) == 0 { return &errortypes.BadInput{ @@ -162,11 +162,11 @@ func buildImpVideo(imp *openrtb.Imp) error { } // not supporting VAST protocol 7 (VAST 4.0); -func cleanProtocol(protocols []openrtb.Protocol) []openrtb.Protocol { - newitems := make([]openrtb.Protocol, 0, len(protocols)) +func cleanProtocol(protocols []openrtb2.Protocol) []openrtb2.Protocol { + newitems := make([]openrtb2.Protocol, 0, len(protocols)) for _, i := range protocols { - if i != openrtb.ProtocolVAST40 { + if i != openrtb2.ProtocolVAST40 { newitems = append(newitems, i) } } @@ -175,7 +175,7 @@ func cleanProtocol(protocols []openrtb.Protocol) []openrtb.Protocol { } // Add EMX required properties to Imp object -func addImpProps(imp *openrtb.Imp, secure *int8, emxExt *openrtb_ext.ExtImpEmxDigital) { +func addImpProps(imp *openrtb2.Imp, secure *int8, emxExt *openrtb_ext.ExtImpEmxDigital) { imp.TagID = emxExt.TagID imp.Secure = secure @@ -202,10 +202,10 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // Handle request errors and formatting to be sent to EMX -func preprocess(request *openrtb.BidRequest) []error { +func preprocess(request *openrtb2.BidRequest) []error { impsCount := len(request.Imp) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) secure := int8(0) domain := "" if request.Site != nil && request.Site.Page != "" { @@ -252,7 +252,7 @@ func preprocess(request *openrtb.BidRequest) []error { } // MakeBids make the bids for the bid response. -func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { // no bid response @@ -265,7 +265,7 @@ func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json index 1281db66fd8..4108c7a524a 100644 --- a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json @@ -61,7 +61,7 @@ "expectedMakeBidsErrors": [ { - "value": "Unable to unpackage bid response\\. Error: json: cannot unmarshal string into Go struct field (Bid\\.seatbid\\.bid\\.w|Bid\\.w) of type uint64", + "value": "Unable to unpackage bid response\\. Error: json: cannot unmarshal string into Go struct field (Bid\\.seatbid\\.bid\\.w|Bid\\.w) of type int64", "comparison": "regex" } ] diff --git a/adapters/engagebdr/engagebdr.go b/adapters/engagebdr/engagebdr.go index 16d20afa611..6c55b99987b 100644 --- a/adapters/engagebdr/engagebdr.go +++ b/adapters/engagebdr/engagebdr.go @@ -4,12 +4,12 @@ import ( "encoding/json" "net/http" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "fmt" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" ) @@ -18,7 +18,7 @@ type EngageBDRAdapter struct { URI string } -func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errors := make([]error, 0, len(request.Imp)) @@ -30,7 +30,7 @@ func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb.BidRequest, reqIn } // EngageBDR uses different sspid parameters for banner and video. - sspidImps := make(map[string][]openrtb.Imp) + sspidImps := make(map[string][]openrtb2.Imp) for _, imp := range request.Imp { if imp.Audio != nil { @@ -92,7 +92,7 @@ func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb.BidRequest, reqIn return adapterRequests, errors } -func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -109,7 +109,7 @@ func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb.BidRequest, e }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -127,7 +127,7 @@ func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb.BidRequest, e return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 8d0f7e8b662..2aa066ba2b2 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -11,7 +11,7 @@ import ( "fmt" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -64,7 +64,7 @@ type hbResponseAd struct { Height uint64 `json:"h,omitempty"` } -func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *EPlanningAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errors := make([]error, 0, len(request.Imp)) totalImps := len(request.Imp) spacesStrings := make([]string, 0, totalImps) @@ -191,8 +191,8 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn return requests, errors } -func isMobileDevice(request *openrtb.BidRequest) bool { - return request.Device != nil && (request.Device.DeviceType == openrtb.DeviceTypeMobileTablet || request.Device.DeviceType == openrtb.DeviceTypePhone || request.Device.DeviceType == openrtb.DeviceTypeTablet) +func isMobileDevice(request *openrtb2.BidRequest) bool { + return request.Device != nil && (request.Device.DeviceType == openrtb2.DeviceTypeMobileTablet || request.Device.DeviceType == openrtb2.DeviceTypePhone || request.Device.DeviceType == openrtb2.DeviceTypeTablet) } func cleanName(name string) string { @@ -202,7 +202,7 @@ func cleanName(name string) string { return name } -func verifyImp(imp *openrtb.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning, error) { +func verifyImp(imp *openrtb2.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { @@ -240,7 +240,7 @@ func verifyImp(imp *openrtb.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning, e return &impExt, nil } -func searchSizePriority(hashedFormats map[string]int, format []openrtb.Format, priorityOrderForSizesAsc []string) (uint64, uint64) { +func searchSizePriority(hashedFormats map[string]int, format []openrtb2.Format, priorityOrderForSizesAsc []string) (int64, int64) { for i := len(priorityOrderForSizesAsc) - 1; i >= 0; i-- { if formatIndex, wasFound := hashedFormats[priorityOrderForSizesAsc[i]]; wasFound { return format[formatIndex].W, format[formatIndex].H @@ -249,7 +249,7 @@ func searchSizePriority(hashedFormats map[string]int, format []openrtb.Format, p return format[0].W, format[0].H } -func getSizeFromImp(imp *openrtb.Imp, isMobile bool) (uint64, uint64) { +func getSizeFromImp(imp *openrtb2.Imp, isMobile bool) (int64, int64) { if imp.Banner.W != nil && imp.Banner.H != nil { return *imp.Banner.W, *imp.Banner.H } @@ -279,7 +279,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -322,15 +322,15 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb.BidRequest, e for _, space := range parsedResponse.Spaces { for _, ad := range space.Ads { if price, err := strconv.ParseFloat(ad.Price, 64); err == nil { - bid := openrtb.Bid{ + bid := openrtb2.Bid{ ID: ad.ImpressionID, AdID: ad.AdID, ImpID: spaceNameToImpID[space.Name], Price: price, AdM: ad.AdM, CrID: ad.CrID, - W: ad.Width, - H: ad.Height, + W: int64(ad.Width), + H: int64(ad.Height), } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ diff --git a/adapters/epom/epom.go b/adapters/epom/epom.go index 4cb9a364cbd..b15c7b1cc04 100644 --- a/adapters/epom/epom.go +++ b/adapters/epom/epom.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -24,7 +24,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { rq, errs := a.makeRequest(request) if len(errs) > 0 { @@ -38,7 +38,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return requests, nil } -func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { if request.Device == nil || request.Device.IP == "" { return nil, []error{&errortypes.BadInput{ Message: "ipv4 address is required field", @@ -61,7 +61,7 @@ func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestDat }, nil } -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -84,7 +84,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -111,7 +111,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index f8681fb93c0..1408fdeeede 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,9 +19,9 @@ type GammaAdapter struct { } type gammaBid struct { - openrtb.Bid //base - VastXML string `json:"vastXml,omitempty"` - VastURL string `json:"vastUrl,omitempty"` + openrtb2.Bid //base + VastXML string `json:"vastXml,omitempty"` + VastURL string `json:"vastUrl,omitempty"` } type gammaSeatBid struct { @@ -30,13 +30,13 @@ type gammaSeatBid struct { Ext json.RawMessage `json:"ext,omitempty"` } type gammaBidResponse struct { - ID string `json:"id"` - SeatBid []gammaSeatBid `json:"seatbid,omitempty"` - BidID string `json:"bidid,omitempty"` - Cur string `json:"cur,omitempty"` - CustomData string `json:"customdata,omitempty"` - NBR *openrtb.NoBidReasonCode `json:"nbr,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` + ID string `json:"id"` + SeatBid []gammaSeatBid `json:"seatbid,omitempty"` + BidID string `json:"bidid,omitempty"` + Cur string `json:"cur,omitempty"` + CustomData string `json:"customdata,omitempty"` + NBR *openrtb2.NoBidReasonCode `json:"nbr,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } func checkParams(gammaExt openrtb_ext.ExtImpGamma) error { @@ -57,7 +57,7 @@ func checkParams(gammaExt openrtb_ext.ExtImpGamma) error { } return nil } -func (a *GammaAdapter) makeRequest(request *openrtb.BidRequest, imp openrtb.Imp) (*adapters.RequestData, []error) { +func (a *GammaAdapter) makeRequest(request *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, []error) { var errors []error var bidderExt adapters.ExtImpBidder @@ -139,7 +139,7 @@ func (a *GammaAdapter) makeRequest(request *openrtb.BidRequest, imp openrtb.Imp) Headers: headers, }, errors } -func (a *GammaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *GammaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { err := &errortypes.BadInput{ @@ -202,8 +202,8 @@ func (a *GammaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } -func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb.Bid { - var bid openrtb.Bid +func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb2.Bid { + var bid openrtb2.Bid bid = gBid.Bid if mediaType == openrtb_ext.BidTypeVideo { @@ -224,7 +224,7 @@ func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb.Bid { return &bid } -func (a *GammaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *GammaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -283,7 +283,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // getMediaTypeForImp figures out which media type this bid is for. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner //default type for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/gamoshi/gamoshi.go b/adapters/gamoshi/gamoshi.go index c0791e66ddc..50b7e9d58a5 100644 --- a/adapters/gamoshi/gamoshi.go +++ b/adapters/gamoshi/gamoshi.go @@ -7,7 +7,7 @@ import ( "strconv" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type GamoshiAdapter struct { URI string } -func (a *GamoshiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *GamoshiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { @@ -125,7 +125,7 @@ func (a *GamoshiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap }}, errors } -func (a *GamoshiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *GamoshiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -143,7 +143,7 @@ func (a *GamoshiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %v. ", err), @@ -169,7 +169,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func getMediaType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impId { if imp.Video != nil { diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index e70b1ddd632..69bc1500bf9 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type GridAdapter struct { endpoint string } -func processImp(imp *openrtb.Imp) error { +func processImp(imp *openrtb2.Imp) error { // get the grid extension var ext adapters.ExtImpBidder var gridExt openrtb_ext.ExtImpGrid @@ -38,13 +38,13 @@ func processImp(imp *openrtb.Imp) error { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *GridAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) // copy the request, because we are going to mutate it requestCopy := *request // this will contain all the valid impressions - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // pre-process the imps for _, imp := range requestCopy.Imp { if err := processImp(&imp); err == nil { @@ -80,7 +80,7 @@ func (a *GridAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapter } // MakeBids unpacks the server's response into Bids. -func (a *GridAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -97,7 +97,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequ }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -129,7 +129,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/grid/gridtest/supplemental/bad_response.json b/adapters/grid/gridtest/supplemental/bad_response.json index 87436da7fc1..a9d38368ab2 100644 --- a/adapters/grid/gridtest/supplemental/bad_response.json +++ b/adapters/grid/gridtest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 426e6112655..0334aad9d43 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,9 +20,9 @@ type GumGumAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var validImps []openrtb.Imp - var siteCopy openrtb.Site +func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var validImps []openrtb2.Imp + var siteCopy openrtb2.Site if request.Site != nil { siteCopy = *request.Site } @@ -44,7 +44,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt if siteCopy.Publisher != nil { siteCopy.Publisher.ID = strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64) } else { - siteCopy.Publisher = &openrtb.Publisher{ID: strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64)} + siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64)} } } @@ -80,7 +80,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // MakeBids unpacks the server's response into Bids. -func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (g *GumGumAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -96,7 +96,7 @@ func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Bad server response: %d. ", err), @@ -124,7 +124,7 @@ func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, errs } -func preprocess(imp *openrtb.Imp) (*openrtb_ext.ExtImpGumGum, error) { +func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { err = &errortypes.BadInput{ @@ -169,7 +169,7 @@ func preprocess(imp *openrtb.Imp) (*openrtb_ext.ExtImpGumGum, error) { return &gumgumExt, nil } -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { return openrtb_ext.BidTypeBanner @@ -178,7 +178,7 @@ func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType return openrtb_ext.BidTypeVideo } -func validateVideoParams(video *openrtb.Video) (err error) { +func validateVideoParams(video *openrtb2.Video) (err error) { if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { return &errortypes.BadInput{ Message: "Invalid or missing video field(s)", diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index b1cc3640714..b942336b410 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type ImprovedigitalAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) reqJSON, err := json.Marshal(request) @@ -38,7 +38,7 @@ func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb.BidRequest, reqInf } // MakeBids unpacks the server's response into Bids. -func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -55,7 +55,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -103,7 +103,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/bad_response.json b/adapters/improvedigital/improvedigitaltest/supplemental/bad_response.json index 7099526bd90..7b103f011a1 100644 --- a/adapters/improvedigital/improvedigitaltest/supplemental/bad_response.json +++ b/adapters/improvedigital/improvedigitaltest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 53d80b23fc6..67227d822b3 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -3,7 +3,7 @@ package adapters import ( "fmt" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" @@ -32,7 +32,7 @@ func BuildInfoAwareBidder(bidder Bidder, info config.BidderInfo) Bidder { } } -func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) { +func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) { var allowedMediaTypes parsedSupports if request.Site != nil { @@ -72,7 +72,7 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *Ext // pruneImps trims invalid media types from each imp, and returns true if any of the // Imps have _no_ valid Media Types left. -func pruneImps(imps []openrtb.Imp, allowedTypes parsedSupports) (int, []error) { +func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports) (int, []error) { numToFilter := 0 var errs []error for i := 0; i < len(imps); i++ { @@ -115,12 +115,12 @@ func parseAllowedTypes(allowedTypes []openrtb_ext.BidType) (allowBanner bool, al return } -func hasAnyTypes(imp *openrtb.Imp) bool { +func hasAnyTypes(imp *openrtb2.Imp) bool { return imp.Banner != nil || imp.Video != nil || imp.Audio != nil || imp.Native != nil } -func filterImps(imps []openrtb.Imp, numToFilter int) ([]openrtb.Imp, []error) { - newImps := make([]openrtb.Imp, 0, len(imps)-numToFilter) +func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) { + newImps := make([]openrtb2.Imp, 0, len(imps)-numToFilter) errs := make([]error, 0, numToFilter) for i := 0; i < len(imps); i++ { if hasAnyTypes(&imps[i]) { diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 6053f18ada9..60e5af041da 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -22,9 +22,9 @@ func TestAppNotSupported(t *testing.T) { }, } constrained := adapters.BuildInfoAwareBidder(bidder, info) - bids, errs := constrained.MakeRequests(&openrtb.BidRequest{ - Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}}, - App: &openrtb.App{}, + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + App: &openrtb2.App{}, }, &adapters.ExtraRequestInfo{}) if !assert.Len(t, errs, 1) { return @@ -44,9 +44,9 @@ func TestSiteNotSupported(t *testing.T) { }, } constrained := adapters.BuildInfoAwareBidder(bidder, info) - bids, errs := constrained.MakeRequests(&openrtb.BidRequest{ - Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}}, - Site: &openrtb.Site{}, + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + Site: &openrtb2.Site{}, }, &adapters.ExtraRequestInfo{}) if !assert.Len(t, errs, 1) { return @@ -73,15 +73,15 @@ func TestImpFiltering(t *testing.T) { testCases := []struct { description string - inBidRequest *openrtb.BidRequest + inBidRequest *openrtb2.BidRequest expectedErrors []error expectedImpLen int }{ { description: "Empty Imp array. MakeRequest() call not expected", - inBidRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{}, - Site: &openrtb.Site{}, + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, + Site: &openrtb2.Site{}, }, expectedErrors: []error{ &errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"}, @@ -90,9 +90,9 @@ func TestImpFiltering(t *testing.T) { }, { description: "Sole imp in bid request is of wrong media type. MakeRequest() call not expected", - inBidRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ID: "imp-1", Video: &openrtb.Video{}}}, - App: &openrtb.App{}, + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Video: &openrtb2.Video{}}}, + App: &openrtb2.App{}, }, expectedErrors: []error{ &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, @@ -102,13 +102,13 @@ func TestImpFiltering(t *testing.T) { }, { description: "All imps in bid request of wrong media type, MakeRequest() call not expected", - inBidRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ - {ID: "imp-1", Video: &openrtb.Video{}}, - {ID: "imp-2", Native: &openrtb.Native{}}, - {ID: "imp-3", Audio: &openrtb.Audio{}}, + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-1", Video: &openrtb2.Video{}}, + {ID: "imp-2", Native: &openrtb2.Native{}}, + {ID: "imp-3", Audio: &openrtb2.Audio{}}, }, - App: &openrtb.App{}, + App: &openrtb2.App{}, }, expectedErrors: []error{ &errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"}, @@ -120,25 +120,25 @@ func TestImpFiltering(t *testing.T) { }, { description: "Some imps with correct media type, MakeRequest() call expected", - inBidRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ { ID: "imp-1", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, }, { - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, }, { ID: "imp-2", - Video: &openrtb.Video{}, - Native: &openrtb.Native{}, + Video: &openrtb2.Video{}, + Native: &openrtb2.Native{}, }, { - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, }, - Site: &openrtb.Site{}, + Site: &openrtb2.Site{}, }, expectedErrors: []error{ &errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"}, @@ -151,12 +151,12 @@ func TestImpFiltering(t *testing.T) { }, { description: "All imps with correct media type, MakeRequest() call expected", - inBidRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ - {ID: "imp-1", Video: &openrtb.Video{}}, - {ID: "imp-2", Video: &openrtb.Video{}}, + inBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-1", Video: &openrtb2.Video{}}, + {ID: "imp-2", Video: &openrtb2.Video{}}, }, - Site: &openrtb.Site{}, + Site: &openrtb2.Site{}, }, expectedErrors: nil, expectedImpLen: 2, @@ -178,10 +178,10 @@ func TestImpFiltering(t *testing.T) { } type mockBidder struct { - gotRequest *openrtb.BidRequest + gotRequest *openrtb2.BidRequest } -func (m *mockBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData for i := 0; i < len(request.Imp); i++ { @@ -191,6 +191,6 @@ func (m *mockBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters return adapterRequests, nil } -func (m *mockBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, []error{errors.New("mock MakeBids error")} } diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index 5d978ec7935..5034f01d77b 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -24,7 +24,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *InMobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *InMobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error if len(request.Imp) == 0 { @@ -56,7 +56,7 @@ func (a *InMobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } -func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *InMobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -67,7 +67,7 @@ func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var serverBidResponse openrtb.BidResponse + var serverBidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &serverBidResponse); err != nil { return nil, []error{err} } @@ -87,7 +87,7 @@ func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, nil } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -117,7 +117,7 @@ func preprocess(imp *openrtb.Imp) error { return nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/invibes/invibes.go b/adapters/invibes/invibes.go index 31124bd108f..fb4d7641d79 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -9,7 +9,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -42,8 +42,8 @@ type InvibesBidParams struct { Properties map[string]InvibesPlacementProperty `json:"Properties"` } type InvibesPlacementProperty struct { - Formats []openrtb.Format `json:"Formats"` - ImpID string `json:"ImpId"` + Formats []openrtb2.Format `json:"Formats"` + ImpID string `json:"ImpId"` } type InvibesInternalParams struct { BidParams InvibesBidParams @@ -61,8 +61,8 @@ type BidServerBidderResponse struct { Error string `json:"error"` } type BidServerTypedBid struct { - Bid openrtb.Bid `json:"bid"` - DealPriority int `json:"dealPriority"` + Bid openrtb2.Bid `json:"bid"` + DealPriority int `json:"dealPriority"` } func (a *InvibesInternalParams) IsTestRequest() bool { @@ -86,7 +86,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return &bidder, nil } -func (a *InvibesAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *InvibesAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var httpRequests []*adapters.RequestData var tempErrors []error gdprApplies, consentString := readGDPR(request) @@ -155,7 +155,7 @@ func (a *InvibesAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return httpRequests, finalErrors } -func readGDPR(request *openrtb.BidRequest) (bool, string) { +func readGDPR(request *openrtb2.BidRequest) (bool, string) { consentString := "" if request.User != nil { var extUser openrtb_ext.ExtUser @@ -175,12 +175,12 @@ func readGDPR(request *openrtb.BidRequest) (bool, string) { return gdprApplies, consentString } -func readAdFormats(currentBanner openrtb.Banner) []openrtb.Format { - var adFormats []openrtb.Format +func readAdFormats(currentBanner openrtb2.Banner) []openrtb2.Format { + var adFormats []openrtb2.Format if currentBanner.Format != nil { adFormats = currentBanner.Format } else if currentBanner.W != nil && currentBanner.H != nil { - adFormats = []openrtb.Format{ + adFormats = []openrtb2.Format{ { W: *currentBanner.W, H: *currentBanner.H, @@ -190,7 +190,7 @@ func readAdFormats(currentBanner openrtb.Banner) []openrtb.Format { return adFormats } -func (a *InvibesAdapter) makeRequest(invibesParams InvibesInternalParams, reqInfo *adapters.ExtraRequestInfo, existingRequests []*adapters.RequestData, request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *InvibesAdapter) makeRequest(invibesParams InvibesInternalParams, reqInfo *adapters.ExtraRequestInfo, existingRequests []*adapters.RequestData, request *openrtb2.BidRequest) (*adapters.RequestData, error) { url, err := a.makeURL(request, invibesParams.DomainID) if err != nil { @@ -233,7 +233,7 @@ func (a *InvibesAdapter) makeRequest(invibesParams InvibesInternalParams, reqInf }, nil } -func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, request *openrtb.BidRequest) (*InvibesAdRequest, error) { +func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, request *openrtb2.BidRequest) (*InvibesAdRequest, error) { var lid string = "" if request.User != nil && request.User.BuyerUID != "" { lid = request.User.BuyerUID @@ -247,11 +247,11 @@ func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, requ var width, height string if request.Device != nil { if request.Device.W > 0 { - width = strconv.FormatUint(request.Device.W, 10) + width = strconv.FormatInt(request.Device.W, 10) } if request.Device.H > 0 { - height = strconv.FormatUint(request.Device.H, 10) + height = strconv.FormatInt(request.Device.H, 10) } } @@ -278,7 +278,7 @@ func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, requ return &invRequest, err } -func (a *InvibesAdapter) makeURL(request *openrtb.BidRequest, domainID int) (string, error) { +func (a *InvibesAdapter) makeURL(request *openrtb2.BidRequest, domainID int) (string, error) { host := "bid.videostep.com" if domainID == 1 { host = "adweb.videostepstage.com" @@ -307,7 +307,7 @@ func (a *InvibesAdapter) makeURL(request *openrtb.BidRequest, domainID int) (str } func (a *InvibesAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 96cd988de54..038a2c146b9 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -8,14 +8,13 @@ import ( "io/ioutil" "net/http" - "golang.org/x/net/context/ctxhttp" - - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) type IxAdapter struct { @@ -46,8 +45,8 @@ type ixBidResult struct { type callOneObject struct { requestJSON bytes.Buffer - width uint64 - height uint64 + width int64 + height int64 bidType string } @@ -87,11 +86,11 @@ func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.P thisImp.TagID = unit.Code if thisImp.Banner != nil { - thisImp.Banner.Format = []openrtb.Format{format} + thisImp.Banner.Format = []openrtb2.Format{format} thisImp.Banner.W = &format.W thisImp.Banner.H = &format.H } - indexReq.Imp = []openrtb.Imp{thisImp} + indexReq.Imp = []openrtb2.Imp{thisImp} // Index spec says "adunit path representing ad server inventory" but we don't have this // ext is DFP div ID and KV pairs if avail //indexReq.Imp[i].Ext = json.RawMessage("{}") @@ -99,7 +98,7 @@ func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.P if indexReq.Site != nil { // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. siteCopy := *indexReq.Site - siteCopy.Publisher = &openrtb.Publisher{ID: params.SiteID} + siteCopy.Publisher = &openrtb2.Publisher{ID: params.SiteID} indexReq.Site = &siteCopy } @@ -221,7 +220,7 @@ func (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidRes } result.ResponseBody = string(body) - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return result, &errortypes.BadServerResponse{ @@ -251,7 +250,7 @@ func (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidRes return result, nil } -func (a *IxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { request.Imp = request.Imp[:a.maxRequests] @@ -286,8 +285,8 @@ func (a *IxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters. formats := getBannerFormats(&banner) for iFmt := range formats { banner.Format = formats[iFmt : iFmt+1] - banner.W = openrtb.Uint64Ptr(banner.Format[0].W) - banner.H = openrtb.Uint64Ptr(banner.Format[0].H) + banner.W = openrtb2.Int64Ptr(banner.Format[0].W) + banner.H = openrtb2.Int64Ptr(banner.Format[0].H) if requestData, err := createRequestData(a, request, &headers); err == nil { if iFmt == 0 { requests = append(requests, requestData) @@ -312,12 +311,12 @@ func (a *IxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters. return append(requests, multiSizeRequests...), errs } -func setSitePublisherId(request *openrtb.BidRequest, iImp int) error { +func setSitePublisherId(request *openrtb2.BidRequest, iImp int) error { if iImp == 0 { // first impression - create a site and pub copy site := *request.Site if site.Publisher == nil { - site.Publisher = &openrtb.Publisher{} + site.Publisher = &openrtb2.Publisher{} } else { publisher := *site.Publisher site.Publisher = &publisher @@ -339,14 +338,14 @@ func setSitePublisherId(request *openrtb.BidRequest, iImp int) error { return nil } -func getBannerFormats(banner *openrtb.Banner) []openrtb.Format { +func getBannerFormats(banner *openrtb2.Banner) []openrtb2.Format { if len(banner.Format) == 0 && banner.W != nil && banner.H != nil { - banner.Format = []openrtb.Format{{W: *banner.W, H: *banner.H}} + banner.Format = []openrtb2.Format{{W: *banner.W, H: *banner.H}} } return banner.Format } -func createRequestData(a *IxAdapter, request *openrtb.BidRequest, headers *http.Header) (*adapters.RequestData, error) { +func createRequestData(a *IxAdapter, request *openrtb2.BidRequest, headers *http.Header) (*adapters.RequestData, error) { body, err := json.Marshal(request) return &adapters.RequestData{ Method: "POST", @@ -356,7 +355,7 @@ func createRequestData(a *IxAdapter, request *openrtb.BidRequest, headers *http. }, err } -func (a *IxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { switch { case response.StatusCode == http.StatusNoContent: return nil, nil @@ -370,7 +369,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReques }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("JSON parsing error: %v", err), diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index 8a75e6852b7..4c0515b4c6b 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -39,7 +39,7 @@ func getAdUnit() pbs.PBSAdUnit { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -54,7 +54,7 @@ func getVideoAdUnit() pbs.PBSAdUnit { Code: "unitCodeVideo", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, BidID: "bididvideo", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 100, H: 75, @@ -73,8 +73,8 @@ func getVideoAdUnit() pbs.PBSAdUnit { } } -func getOpenRTBBid(i openrtb.Imp) openrtb.Bid { - return openrtb.Bid{ +func getOpenRTBBid(i openrtb2.Imp) openrtb2.Bid { + return openrtb2.Bid{ ID: fmt.Sprintf("%d", rand.Intn(1000)), ImpID: i.ID, Price: 1.0, @@ -94,7 +94,7 @@ func dummyIXServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -103,10 +103,10 @@ func dummyIXServer(w http.ResponseWriter, r *http.Request) { impression := breq.Imp[0] - resp := openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ getOpenRTBBid(impression), }, }, @@ -141,7 +141,7 @@ func TestIxInvalidCall(t *testing.T) { func TestIxInvalidCallReqAppNil(t *testing.T) { ctx := context.TODO() pbReq := pbs.PBSRequest{ - App: &openrtb.App{}, + App: &openrtb2.App{}, } pbBidder := pbs.PBSBidder{} @@ -203,7 +203,7 @@ func TestIxTimeoutMultipleSlots(t *testing.T) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -212,10 +212,10 @@ func TestIxTimeoutMultipleSlots(t *testing.T) { impression := breq.Imp[0] - resp := openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ getOpenRTBBid(impression), }, }, @@ -247,7 +247,7 @@ func TestIxTimeoutMultipleSlots(t *testing.T) { adUnit1 := getAdUnit() adUnit2 := getAdUnit() adUnit2.Code = "unitCode2" - adUnit2.Sizes = []openrtb.Format{ + adUnit2.Sizes = []openrtb2.Format{ { W: 8, H: 10, @@ -382,7 +382,7 @@ func TestIxInvalidCallMissingSize(t *testing.T) { ctx := context.TODO() pbReq := pbs.PBSRequest{} adUnit := getAdUnit() - adUnit.Sizes = []openrtb.Format{} + adUnit.Sizes = []openrtb2.Format{} pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ @@ -423,17 +423,17 @@ func TestIxMismatchUnitCode(t *testing.T) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ { ID: fmt.Sprintf("%d", rand.Intn(1000)), ImpID: "unitCode_bogus", @@ -477,14 +477,14 @@ func TestNoSeatBid(t *testing.T) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := openrtb.BidResponse{} + resp := openrtb2.BidResponse{} js, err := json.Marshal(resp) if err != nil { @@ -516,15 +516,15 @@ func TestNoSeatBidBid(t *testing.T) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ {}, }, } @@ -650,7 +650,7 @@ func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { ctx := context.TODO() pbReq := pbs.PBSRequest{} adUnit := getAdUnit() - adUnit.Sizes = append(adUnit.Sizes, openrtb.Format{W: 20, H: 22}) + adUnit.Sizes = append(adUnit.Sizes, openrtb2.Format{W: 20, H: 22}) pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", @@ -674,7 +674,7 @@ func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { } } -func bidResponseForSizeExist(bids pbs.PBSBidSlice, h uint64, w uint64) bool { +func bidResponseForSizeExist(bids pbs.PBSBidSlice, h, w int64) bool { for _, v := range bids { if v.Height == h && v.Width == w { return true diff --git a/adapters/jixie/jixie.go b/adapters/jixie/jixie.go index 973bfcb42eb..565f8a588c3 100644 --- a/adapters/jixie/jixie.go +++ b/adapters/jixie/jixie.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs = make([]error, 0) data, err := json.Marshal(request) @@ -80,7 +80,7 @@ func getBidType(bidAdm string) openrtb_ext.BidType { } // MakeBids make the bids for the bid response. -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { // no bid response @@ -99,7 +99,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index fda6869e12f..670bfb80b49 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type KidozAdapter struct { endpoint string } -func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *KidozAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -102,7 +102,7 @@ func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.Ext return result, errs } -func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *KidozAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error switch responseData.StatusCode { @@ -129,7 +129,7 @@ func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.Request }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse err := json.Unmarshal(responseData.Body, &bidResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -169,7 +169,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters const UndefinedMediaType = openrtb_ext.BidType("") -func GetMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { var bidType openrtb_ext.BidType = UndefinedMediaType for _, impression := range imps { if impression.ID != impID { diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index 5830f60ddb5..0de06392bba 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -24,14 +24,14 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "kidoztest", bidder) } -func makeBidRequest() *openrtb.BidRequest { - request := &openrtb.BidRequest{ +func makeBidRequest() *openrtb2.BidRequest { + request := &openrtb2.BidRequest{ ID: "test-req-id-0", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "test-imp-id-0", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 320, H: 50, @@ -89,22 +89,22 @@ func TestMakeBids(t *testing.T) { } func TestGetMediaTypeForImp(t *testing.T) { - imps := []openrtb.Imp{ + imps := []openrtb2.Imp{ { ID: "1", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "2", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, }, { ID: "3", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, }, { ID: "4", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, }, } diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index d05b6e7979a..54e89200844 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getHeaders(request *openrtb.BidRequest) *http.Header { +func getHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -60,7 +60,7 @@ func getHeaders(request *openrtb.BidRequest) *http.Header { } func (a *KrushmediaAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -107,7 +107,7 @@ func (a *KrushmediaAdapter) MakeRequests( }}, nil } -func (a *KrushmediaAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtKrushmedia, error) { +func (a *KrushmediaAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtKrushmedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -129,7 +129,7 @@ func (a *KrushmediaAdapter) buildEndpointURL(params *openrtb_ext.ExtKrushmedia) } func (a *KrushmediaAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -157,7 +157,7 @@ func (a *KrushmediaAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -176,7 +176,7 @@ func (a *KrushmediaAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go index a995586533a..8b011b7f795 100644 --- a/adapters/kubient/kubient.go +++ b/adapters/kubient/kubient.go @@ -5,10 +5,10 @@ import ( "fmt" "net/http" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" ) @@ -28,7 +28,7 @@ type KubientAdapter struct { // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (adapter *KubientAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ([]*adapters.RequestData, []error) { if len(openRTBRequest.Imp) == 0 { @@ -65,7 +65,7 @@ func (adapter *KubientAdapter) MakeRequests( return requestsToBidder, errs } -func checkImpExt(impObj openrtb.Imp) error { +func checkImpExt(impObj openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(impObj.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -87,7 +87,7 @@ func checkImpExt(impObj openrtb.Imp) error { } // MakeBids makes the bids -func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -106,7 +106,7 @@ func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -131,7 +131,7 @@ func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/kubient/kubienttest/supplemental/bad_response.json index 076acf29058..832dc975088 100644 --- a/adapters/kubient/kubienttest/supplemental/bad_response.json +++ b/adapters/kubient/kubienttest/supplemental/bad_response.json @@ -54,7 +54,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/lifestreet/lifestreet.go b/adapters/lifestreet/lifestreet.go index 4dbd7baed62..7114a9bbaac 100644 --- a/adapters/lifestreet/lifestreet.go +++ b/adapters/lifestreet/lifestreet.go @@ -9,9 +9,9 @@ import ( "net/http" "strings" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/pbs" @@ -63,7 +63,7 @@ func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, re return } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return @@ -98,11 +98,11 @@ func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, re return } -func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb.BidRequest, error) { +func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb2.BidRequest, error) { lsReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), []pbs.MediaType{mtype}) if err != nil { - return openrtb.BidRequest{}, err + return openrtb2.BidRequest{}, err } if lsReq.Imp != nil && len(lsReq.Imp) > 0 { diff --git a/adapters/lifestreet/lifestreet_test.go b/adapters/lifestreet/lifestreet_test.go index 412333c3714..215f137ccf4 100644 --- a/adapters/lifestreet/lifestreet_test.go +++ b/adapters/lifestreet/lifestreet_test.go @@ -10,13 +10,13 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" "fmt" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" ) @@ -53,7 +53,7 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -84,7 +84,7 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Model '%s' doesn't match '%s", breq.Device.Model, lsdata.deviceModel), http.StatusInternalServerError) return } - if *breq.Device.ConnectionType != openrtb.ConnectionType(lsdata.deviceConnectiontype) { + if *breq.Device.ConnectionType != openrtb2.ConnectionType(lsdata.deviceConnectiontype) { http.Error(w, fmt.Sprintf("Connectiontype '%d' doesn't match '%d", breq.Device.ConnectionType, lsdata.deviceConnectiontype), http.StatusInternalServerError) return } @@ -96,24 +96,24 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Wrong number of imp objects sent: %d", len(breq.Imp)), http.StatusInternalServerError) return } - var bid *openrtb.Bid + var bid *openrtb2.Bid for _, tag := range lsdata.tags { if breq.Imp[0].Banner == nil { http.Error(w, fmt.Sprintf("No banner object sent"), http.StatusInternalServerError) return } - if *breq.Imp[0].Banner.W != lsdata.width || *breq.Imp[0].Banner.H != lsdata.height { + if *breq.Imp[0].Banner.W != int64(lsdata.width) || *breq.Imp[0].Banner.H != int64(lsdata.height) { http.Error(w, fmt.Sprintf("Size '%dx%d' doesn't match '%dx%d", breq.Imp[0].Banner.W, breq.Imp[0].Banner.H, lsdata.width, lsdata.height), http.StatusInternalServerError) return } if breq.Imp[0].TagID == tag.slotTag { - bid = &openrtb.Bid{ + bid = &openrtb2.Bid{ ID: "random-id", ImpID: breq.Imp[0].ID, Price: tag.bid, AdM: tag.content, - W: lsdata.width, - H: lsdata.height, + W: int64(lsdata.width), + H: int64(lsdata.height), } } } @@ -122,14 +122,14 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "2345676337", BidID: "975537589956", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "LSM", - Bid: []openrtb.Bid{*bid}, + Bid: []openrtb2.Bid{*bid}, }, }, } @@ -181,25 +181,25 @@ func TestLifestreetBasicResponse(t *testing.T) { pbin := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 2), - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: lsdata.appBundle, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: lsdata.deviceUA, IP: lsdata.deviceIP, Make: lsdata.deviceMake, Model: lsdata.deviceModel, - ConnectionType: openrtb.ConnectionType(lsdata.deviceConnectiontype).Ptr(), + ConnectionType: openrtb2.ConnectionType(lsdata.deviceConnectiontype).Ptr(), IFA: lsdata.deviceIfa, }, } for i, tag := range lsdata.tags { pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { - W: lsdata.width, - H: lsdata.height, + W: int64(lsdata.width), + H: int64(lsdata.height), }, }, Bids: []pbs.Bids{ @@ -266,7 +266,7 @@ func TestLifestreetBasicResponse(t *testing.T) { if bid.Price != tag.bid { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) } - if bid.Width != lsdata.width || bid.Height != lsdata.height { + if bid.Width != int64(lsdata.width) || bid.Height != int64(lsdata.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, lsdata.width, lsdata.height) } if bid.Adm != tag.content { diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index a81313c6e80..483ffbd5bd6 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type LockerDomeAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids [from the bidder, in this case, LockerDome] -func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidRequest, extraReqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { +func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, extraReqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { numberOfImps := len(openRTBRequest.Imp) @@ -70,7 +70,7 @@ func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidReques indexesOfValidImps = append(indexesOfValidImps, i) } if numberOfImps > len(indexesOfValidImps) { - var validImps []openrtb.Imp + var validImps []openrtb2.Imp for j := 0; j < len(indexesOfValidImps); j++ { validImps = append(validImps, openRTBRequest.Imp[j]) } @@ -109,7 +109,7 @@ func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidReques } // MakeBids unpacks the server's response into Bids. -func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { +func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { if bidderRawResponse.StatusCode == http.StatusNoContent { return nil, nil @@ -127,7 +127,7 @@ func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb.BidRequest, r }} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{ fmt.Errorf("Error unmarshaling LockerDome bid response - %s", err.Error()), diff --git a/adapters/lockerdome/lockerdometest/supplemental/bad_response.json b/adapters/lockerdome/lockerdometest/supplemental/bad_response.json index 8df8a0e1633..d119a47da80 100644 --- a/adapters/lockerdome/lockerdometest/supplemental/bad_response.json +++ b/adapters/lockerdome/lockerdometest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "Error unmarshaling LockerDome bid response - json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Error unmarshaling LockerDome bid response - json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index 30d9dd5df4c..bc45652d127 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type LogicadAdapter struct { endpoint string } -func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *LogicadAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{Message: "No impression in the bid request"}} } @@ -38,10 +38,10 @@ func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return result, errs } -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpLogicad][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLogicad][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -55,7 +55,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]ope } if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) resImps = append(resImps, imp) @@ -70,7 +70,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpLogicad) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) { +func getImpressionExt(imp *openrtb2.Imp) (openrtb_ext.ExtImpLogicad, error) { var bidderExt adapters.ExtImpBidder var logicadExt openrtb_ext.ExtImpLogicad @@ -87,7 +87,7 @@ func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) { return logicadExt, nil } -func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -105,7 +105,7 @@ func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.Bid Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -117,7 +117,7 @@ func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext. } //MakeBids translates Logicad bid response to prebid-server specific format -func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -126,7 +126,7 @@ func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg := fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index 289d062c8bb..a8040365964 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type LunaMediaAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) @@ -49,10 +49,10 @@ func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqIn } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -71,7 +71,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]o continue } if res[*impExt] == nil { - res[*impExt] = make([]openrtb.Imp, 0) + res[*impExt] = make([]openrtb2.Imp, 0) } res[*impExt] = append(res[*impExt], imp) resImps = append(resImps, imp) @@ -87,7 +87,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error { } //Alter impression info to comply with LunaMedia platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to LunaMedia platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -95,7 +95,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner @@ -114,7 +114,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -130,7 +130,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { return &LunaMediaExt, nil } -func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -154,7 +154,7 @@ func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.B Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -184,7 +184,7 @@ func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLuna } //MakeBids translates LunaMedia bid response to prebid-server specific format -func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { return nil, nil @@ -194,7 +194,7 @@ func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, e return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} @@ -218,7 +218,7 @@ func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, e } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Video != nil { return openrtb_ext.BidTypeVideo diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index eb1e8da0858..20d0d1fc645 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type MarsmediaAdapter struct { URI string } -func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { request := *requestIn @@ -106,7 +106,7 @@ func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo * }}, []error{} } -func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -124,7 +124,7 @@ func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Bad server response: %d. ", err), @@ -150,7 +150,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // getMediaTypeForImp figures out which media type this bid is for. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner //default type for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index d6ba175d336..0917003ab82 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ type RespBidExt struct { CreativeType openrtb_ext.BidType `json:"crtype"` } -func (a *MgidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (adapterRequests []*adapters.RequestData, errs []error) { +func (a *MgidAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (adapterRequests []*adapters.RequestData, errs []error) { adapterReq, errs := a.makeRequest(request) if adapterReq != nil && len(errs) == 0 { @@ -36,7 +36,7 @@ func (a *MgidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapter return } -func (a *MgidAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *MgidAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error path, err := preprocess(request) @@ -65,7 +65,7 @@ func (a *MgidAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reques } // Mutate the request to get it ready to send to yieldmo. -func preprocess(request *openrtb.BidRequest) (path string, err error) { +func preprocess(request *openrtb2.BidRequest) (path string, err error) { if request.TMax == 0 { request.TMax = 200 } @@ -123,7 +123,7 @@ func preprocess(request *openrtb.BidRequest) (path string, err error) { return } -func (a *MgidAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *MgidAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -140,7 +140,7 @@ func (a *MgidAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.Requ }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 6982e419cd5..d39fbfb74e9 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -37,7 +37,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests create bid request for mobfoxpb demand -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var route string var method string @@ -67,7 +67,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex requestURI = strings.Replace(requestURI, MACROS_ROUTE, route, 1) requestURI = strings.Replace(requestURI, MACROS_METHOD, method, 1) - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} adapterReq, err := a.makeRequest(&reqCopy, requestURI) if err != nil { errs = append(errs, err) @@ -78,7 +78,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return adapterRequests, errs } -func (a *adapter) makeRequest(request *openrtb.BidRequest, requestURI string) (*adapters.RequestData, error) { +func (a *adapter) makeRequest(request *openrtb2.BidRequest, requestURI string) (*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) if err != nil { @@ -97,7 +97,7 @@ func (a *adapter) makeRequest(request *openrtb.BidRequest, requestURI string) (* } // MakeBids makes the bids -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -110,7 +110,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -134,7 +134,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json index efd1ac90d9d..d61cb8837c4 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json @@ -78,7 +78,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index 76c18e27007..217f0f24b7b 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData adapterRequest, errs := adapter.makeRequest(request) @@ -44,7 +44,7 @@ func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb.BidRequest, reqI return adapterRequests, errs } -func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, outgoingRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest, outgoingRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -61,7 +61,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, }} } - var incomingBidResponse openrtb.BidResponse + var incomingBidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &incomingBidResponse); err != nil { return nil, []error{err} @@ -81,7 +81,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, return outgoingBidResponse, nil } -func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest) @@ -124,7 +124,7 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb.BidRequest) (* }, errs } -func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { +func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse var errs []error @@ -167,8 +167,8 @@ func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) return url, nil } -func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb.Imp { - var validImps []openrtb.Imp +func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb2.Imp { + var validImps []openrtb2.Imp for _, imp := range bidRequest.Imp { if imp.Banner != nil || imp.Video != nil { @@ -187,7 +187,7 @@ func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb.BidRequest, e return validImps } -func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb.Imp) openrtb_ext.BidType { +func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb2.Imp) openrtb_ext.BidType { if imps[0].Video != nil { return openrtb_ext.BidTypeVideo } diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go index 7189543e0f8..afe80bcbfee 100644 --- a/adapters/nanointeractive/nanointeractive.go +++ b/adapters/nanointeractive/nanointeractive.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,10 +16,10 @@ type NanoInteractiveAdapter struct { endpoint string } -func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp var adapterRequests []*adapters.RequestData var referer string = "" @@ -47,7 +47,7 @@ func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, re // set referer origin if referer != "" { if bidRequest.Site == nil { - bidRequest.Site = &openrtb.Site{} + bidRequest.Site = &openrtb2.Site{} } bidRequest.Site.Ref = referer } @@ -88,7 +88,7 @@ func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, re } func (a *NanoInteractiveAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -104,7 +104,7 @@ func (a *NanoInteractiveAdapter) MakeBids( }} } - var openRtbBidResponse openrtb.BidResponse + var openRtbBidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -129,7 +129,7 @@ func (a *NanoInteractiveAdapter) MakeBids( return bidResponse, nil } -func checkImp(imp *openrtb.Imp) (string, error) { +func checkImp(imp *openrtb2.Imp) (string, error) { // We support only banner impression if imp.Banner == nil { return "", fmt.Errorf("invalid MediaType. NanoInteractive only supports Banner type. ImpID=%s", imp.ID) diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go index 03095ee6c44..e819a2787a2 100755 --- a/adapters/ninthdecimal/ninthdecimal.go +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type NinthDecimalAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) @@ -49,10 +49,10 @@ func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb.BidRequest, re } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -71,7 +71,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpNinthDecimal] continue } if res[*impExt] == nil { - res[*impExt] = make([]openrtb.Imp, 0) + res[*impExt] = make([]openrtb2.Imp, 0) } res[*impExt] = append(res[*impExt], imp) resImps = append(resImps, imp) @@ -87,7 +87,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { } //Alter impression info to comply with NinthDecimal platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to NinthDecimal platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -95,7 +95,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner @@ -114,7 +114,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -130,7 +130,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) return &NinthDecimalExt, nil } -func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -154,7 +154,7 @@ func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrt Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -184,7 +184,7 @@ func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpN } //MakeBids translates NinthDecimal bid response to prebid-server specific format -func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { return nil, nil @@ -194,7 +194,7 @@ func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} @@ -218,7 +218,7 @@ func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Video != nil { return openrtb_ext.BidTypeVideo diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index 875a7260e80..7fc7669b7c4 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests Makes the OpenRTB request payload -func (a *NoBidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *NoBidAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ @@ -52,7 +52,7 @@ func (a *NoBidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte } // MakeBids makes the bids -func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *NoBidAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -70,7 +70,7 @@ func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -98,7 +98,7 @@ func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, errs } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -106,7 +106,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index ebf428439ed..6069682bc2a 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -31,7 +31,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { pubID := "" for idx, imp := range request.Imp { onetagExt, err := getImpressionExt(imp) @@ -73,7 +73,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapter return []*adapters.RequestData{requestData}, nil } -func getImpressionExt(imp openrtb.Imp) (*openrtb_ext.ExtImpOnetag, error) { +func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpOnetag, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -96,7 +96,7 @@ func (a *adapter) buildEndpointURL(pubID string) (string, error) { return macros.ResolveMacros(a.endpointTemplate, endpointParams) } -func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil } @@ -108,7 +108,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.Re return nil, []error{err} } - var response openrtb.BidResponse + var response openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -131,7 +131,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.Re return bidResponse, nil } -func getMediaTypeForBid(impressions []openrtb.Imp, bid openrtb.Bid) (openrtb_ext.BidType, error) { +func getMediaTypeForBid(impressions []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) { for _, impression := range impressions { if impression.ID == bid.ImpID { if impression.Banner != nil { diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go index 55064ea7d07..bc18b9ebf17 100644 --- a/adapters/openrtb_util.go +++ b/adapters/openrtb_util.go @@ -3,10 +3,9 @@ package adapters import ( "encoding/json" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/pbs" - - "github.com/mxmCherry/openrtb" ) func min(x, y int) int { @@ -37,37 +36,37 @@ func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { return res[:i] } -func makeBanner(unit pbs.PBSAdUnit) *openrtb.Banner { - return &openrtb.Banner{ - W: openrtb.Uint64Ptr(unit.Sizes[0].W), - H: openrtb.Uint64Ptr(unit.Sizes[0].H), +func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { + return &openrtb2.Banner{ + W: openrtb2.Int64Ptr(unit.Sizes[0].W), + H: openrtb2.Int64Ptr(unit.Sizes[0].H), Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data TopFrame: unit.TopFrame, } } -func makeVideo(unit pbs.PBSAdUnit) *openrtb.Video { +func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { // empty mimes array is a sign of uninitialized Video object if len(unit.Video.Mimes) < 1 { return nil } mimes := make([]string, len(unit.Video.Mimes)) copy(mimes, unit.Video.Mimes) - pbm := make([]openrtb.PlaybackMethod, 1) + pbm := make([]openrtb2.PlaybackMethod, 1) //this will become int8 soon, so we only care about the first index in the array - pbm[0] = openrtb.PlaybackMethod(unit.Video.PlaybackMethod) + pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) - protocols := make([]openrtb.Protocol, 0, len(unit.Video.Protocols)) + protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) for _, protocol := range unit.Video.Protocols { - protocols = append(protocols, openrtb.Protocol(protocol)) + protocols = append(protocols, openrtb2.Protocol(protocol)) } - return &openrtb.Video{ + return &openrtb2.Video{ MIMEs: mimes, MinDuration: unit.Video.Minduration, MaxDuration: unit.Video.Maxduration, W: unit.Sizes[0].W, H: unit.Sizes[0].H, - StartDelay: openrtb.StartDelay(unit.Video.Startdelay).Ptr(), + StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), PlaybackMethod: pbm, Protocols: protocols, } @@ -77,8 +76,8 @@ func makeVideo(unit pbs.PBSAdUnit) *openrtb.Video { // // Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. // The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. -func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb.BidRequest, error) { - imps := make([]openrtb.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) +func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { + imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) for _, unit := range bidder.AdUnits { if len(unit.Sizes) <= 0 { continue @@ -88,7 +87,7 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily continue } - newImp := openrtb.Imp{ + newImp := openrtb2.Imp{ ID: unit.Code, Secure: &req.Secure, Instl: unit.Instl, @@ -101,7 +100,7 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily newImp.Video = makeVideo(unit) // It's strange to error here... but preserves legacy behavior in legacy code. See #603. if newImp.Video == nil { - return openrtb.BidRequest{}, &errortypes.BadInput{ + return openrtb2.BidRequest{}, &errortypes.BadInput{ Message: "Invalid AdUnit: VIDEO media type with no video data", } } @@ -113,19 +112,19 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily } if len(imps) < 1 { - return openrtb.BidRequest{}, &errortypes.BadInput{ + return openrtb2.BidRequest{}, &errortypes.BadInput{ Message: "openRTB bids need at least one Imp", } } if req.App != nil { - return openrtb.BidRequest{ + return openrtb2.BidRequest{ ID: req.Tid, Imp: imps, App: req.App, Device: req.Device, User: req.User, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: req.Tid, }, AT: 1, @@ -142,20 +141,20 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily userExt = req.User.Ext } - return openrtb.BidRequest{ + return openrtb2.BidRequest{ ID: req.Tid, Imp: imps, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Domain: req.Domain, Page: req.Url, }, Device: req.Device, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: buyerUID, ID: id, Ext: userExt, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ FD: 1, // upstream, aka header TID: req.Tid, }, @@ -165,8 +164,8 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily }, nil } -func copyFormats(sizes []openrtb.Format) []openrtb.Format { - sizesCopy := make([]openrtb.Format, len(sizes)) +func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { + sizesCopy := make([]openrtb2.Format, len(sizes)) for i := 0; i < len(sizes); i++ { sizesCopy[i] = sizes[i] sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index 2cf67c22537..d859114b646 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -5,7 +5,7 @@ import ( "encoding/json" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" @@ -42,7 +42,7 @@ func TestOpenRTB(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -73,7 +73,7 @@ func TestOpenRTBVideo(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -96,8 +96,8 @@ func TestOpenRTBVideo(t *testing.T) { assert.Equal(t, resp.Imp[0].ID, "unitCode") assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) - assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb.StartDelay(5)) - assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb.PlaybackMethod{openrtb.PlaybackMethod(1)}) + assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) + assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) } @@ -110,7 +110,7 @@ func TestOpenRTBVideoNoVideoData(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -134,7 +134,7 @@ func TestOpenRTBVideoFilteredOut(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -152,7 +152,7 @@ func TestOpenRTBVideoFilteredOut(t *testing.T) { { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -179,7 +179,7 @@ func TestOpenRTBMultiMediaImp(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -215,7 +215,7 @@ func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -237,7 +237,7 @@ func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { assert.Equal(t, len(resp.Imp), 1) assert.Equal(t, resp.Imp[0].ID, "unitCode") assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video, (*openrtb.Video)(nil)) + assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) } func TestOpenRTBNoSize(t *testing.T) { @@ -267,20 +267,20 @@ func TestOpenRTBMobile(t *testing.T) { MaxKeyLength: 20, Secure: 1, TimeoutMillis: 1000, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "1995257847363113", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "test_ua", IP: "test_ip", Make: "test_make", Model: "test_model", IFA: "test_ifa", }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: "test_buyeruid", }, } @@ -290,7 +290,7 @@ func TestOpenRTBMobile(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, @@ -318,7 +318,7 @@ func TestOpenRTBMobile(t *testing.T) { func TestOpenRTBEmptyUser(t *testing.T) { pbReq := pbs.PBSRequest{ - User: &openrtb.User{}, + User: &openrtb2.User{}, } pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", @@ -326,7 +326,7 @@ func TestOpenRTBEmptyUser(t *testing.T) { { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -337,14 +337,14 @@ func TestOpenRTBEmptyUser(t *testing.T) { } resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User, &openrtb.User{}) + assert.EqualValues(t, resp.User, &openrtb2.User{}) } func TestOpenRTBUserWithCookie(t *testing.T) { pbsCookie := usersync.NewPBSCookie() pbsCookie.TrySync("test", "abcde") pbReq := pbs.PBSRequest{ - User: &openrtb.User{}, + User: &openrtb2.User{}, } pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", @@ -352,7 +352,7 @@ func TestOpenRTBUserWithCookie(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, @@ -368,7 +368,7 @@ func TestOpenRTBUserWithCookie(t *testing.T) { } func TestSizesCopy(t *testing.T) { - formats := []openrtb.Format{ + formats := []openrtb2.Format{ { W: 10, }, @@ -402,7 +402,7 @@ func TestMakeVideo(t *testing.T) { adUnit := pbs.PBSAdUnit{ Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -421,7 +421,7 @@ func TestMakeVideo(t *testing.T) { video := makeVideo(adUnit) assert.EqualValues(t, video.MinDuration, 15) assert.EqualValues(t, video.MaxDuration, 30) - assert.EqualValues(t, *video.StartDelay, openrtb.StartDelay(5)) + assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) assert.EqualValues(t, len(video.PlaybackMethod), 1) assert.EqualValues(t, len(video.Protocols), 4) } @@ -435,10 +435,10 @@ func TestGDPR(t *testing.T) { regsExt, _ := json.Marshal(rawRegsExt) pbReq := pbs.PBSRequest{ - User: &openrtb.User{ + User: &openrtb2.User{ Ext: userExt, }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: regsExt, }, } @@ -449,7 +449,7 @@ func TestGDPR(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -486,24 +486,24 @@ func TestGDPRMobile(t *testing.T) { MaxKeyLength: 20, Secure: 1, TimeoutMillis: 1000, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "1995257847363113", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "test_ua", IP: "test_ip", Make: "test_make", Model: "test_model", IFA: "test_ifa", }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: "test_buyeruid", Ext: userExt, }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: regsExt, }, } @@ -513,7 +513,7 @@ func TestGDPRMobile(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index b6640b1f5e9..c8c0de8d9d2 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -28,10 +28,10 @@ type openxReqExt struct { BidderConfig string `json:"bc"` } -func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *OpenxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var bannerImps []openrtb.Imp - var videoImps []openrtb.Imp + var bannerImps []openrtb2.Imp + var videoImps []openrtb2.Imp for _, imp := range request.Imp { // OpenX doesn't allow multi-type imp. Banner takes priority over video. @@ -55,7 +55,7 @@ func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte // OpenX only supports single imp video request for _, videoImp := range videoImps { - reqCopy.Imp = []openrtb.Imp{videoImp} + reqCopy.Imp = []openrtb2.Imp{videoImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -66,9 +66,9 @@ func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } -func (a *OpenxAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *OpenxAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp reqExt := openxReqExt{BidderConfig: hbconfig} for _, imp := range request.Imp { @@ -111,7 +111,7 @@ func (a *OpenxAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reque } // Mutate the imp to get it ready to send to openx. -func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { +func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -158,7 +158,7 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { return nil } -func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -175,7 +175,7 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -202,7 +202,7 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq // // OpenX doesn't support multi-type impressions. // If both banner and video exist, take banner as we do not want in-banner video. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index 81d2ff227ec..aaf7ecf85a2 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -41,10 +41,10 @@ func assertCurrencyInBidResponse(t *testing.T, expectedCurrency string, currency t.Fatalf("Builder returned unexpected error %v", buildErr) } - prebidRequest := &openrtb.BidRequest{ - Imp: []openrtb.Imp{}, + prebidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, } - mockedBidResponse := &openrtb.BidResponse{} + mockedBidResponse := &openrtb2.BidResponse{} if currency != nil { mockedBidResponse.Cur = *currency } diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index e26ad4efc0c..b0b3bcfbb7a 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,9 +17,9 @@ type OrbidderAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids from orbidder. -func (rcv *OrbidderAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (rcv *OrbidderAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // check if imps exists, if not return error and do send request to orbidder. if len(request.Imp) == 0 { @@ -62,7 +62,7 @@ func (rcv *OrbidderAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a }}, errs } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -81,7 +81,7 @@ func preprocess(imp *openrtb.Imp) error { } // MakeBids unpacks server response into Bids. -func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -104,7 +104,7 @@ func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index 97f9b542734..cc215412a5f 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -41,7 +41,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters /* MakeRequests */ -func getAdType(imp openrtb.Imp, parsedImpExt *wrappedExtImpBidder) int { +func getAdType(imp openrtb2.Imp, parsedImpExt *wrappedExtImpBidder) int { // video if imp.Video != nil { if parsedImpExt != nil && parsedImpExt.Prebid != nil && parsedImpExt.Prebid.IsRewardedInventory == 1 { @@ -67,7 +67,7 @@ func getAdType(imp openrtb.Imp, parsedImpExt *wrappedExtImpBidder) int { return -1 } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var requests []*adapters.RequestData var errs []error @@ -98,7 +98,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapter continue } - requestCopy.Imp = []openrtb.Imp{imp} + requestCopy.Imp = []openrtb2.Imp{imp} requestJSON, err := json.Marshal(requestCopy) if err != nil { errs = append(errs, err) @@ -122,7 +122,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapter /* MakeBids */ -func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) { +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { if bid == nil { return "", fmt.Errorf("the bid request object is nil") } @@ -150,7 +150,7 @@ func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) { return "", fmt.Errorf("unrecognized adtype in response") } -func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil } @@ -169,7 +169,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.Re return nil, []error{err} } - var response openrtb.BidResponse + var response openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index be3ebb313f3..f3f5785492d 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -158,8 +158,8 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - pbReq.Imp[i].Banner.H = openrtb.Uint64Ptr(uint64(height)) - pbReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) + pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) + pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) if len(params.Keywords) != 0 { kvstr := prepareImpressionExt(params.Keywords) @@ -190,12 +190,12 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder if pbReq.Site != nil { siteCopy := *pbReq.Site - siteCopy.Publisher = &openrtb.Publisher{ID: params.PublisherId, Domain: req.Domain} + siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} pbReq.Site = &siteCopy } if pbReq.App != nil { appCopy := *pbReq.App - appCopy.Publisher = &openrtb.Publisher{ID: params.PublisherId, Domain: req.Domain} + appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} pbReq.App = &appCopy } } @@ -264,7 +264,7 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder debug.ResponseBody = string(body) } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ @@ -314,7 +314,7 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder return bids, nil } -func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error @@ -343,7 +343,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada publisherCopy.ID = pubID siteCopy.Publisher = &publisherCopy } else { - siteCopy.Publisher = &openrtb.Publisher{ID: pubID} + siteCopy.Publisher = &openrtb2.Publisher{ID: pubID} } request.Site = &siteCopy } else if request.App != nil { @@ -353,7 +353,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada publisherCopy.ID = pubID appCopy.Publisher = &publisherCopy } else { - appCopy.Publisher = &openrtb.Publisher{ID: pubID} + appCopy.Publisher = &openrtb2.Publisher{ID: pubID} } request.App = &appCopy } @@ -384,7 +384,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada // validateAdslot validate the optional adslot string // valid formats are 'adslot@WxH', 'adslot' and no adslot -func validateAdSlot(adslot string, imp *openrtb.Imp) error { +func validateAdSlot(adslot string, imp *openrtb2.Imp) error { adSlotStr := strings.TrimSpace(adslot) if len(adSlotStr) == 0 { @@ -418,7 +418,7 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { //In case of video, size could be derived from the player size if imp.Banner != nil { - imp.Banner = assignBannerHeightAndWidth(imp.Banner, uint64(height), uint64(width)) + imp.Banner = assignBannerHeightAndWidth(imp.Banner, int64(height), int64(width)) } } else { return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) @@ -427,7 +427,7 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { return nil } -func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { +func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W != nil && banner.H != nil { return banner, nil } @@ -439,17 +439,17 @@ func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { return assignBannerHeightAndWidth(banner, banner.Format[0].H, banner.Format[0].H), nil } -func assignBannerHeightAndWidth(banner *openrtb.Banner, h uint64, w uint64) *openrtb.Banner { +func assignBannerHeightAndWidth(banner *openrtb2.Banner, h, w int64) *openrtb2.Banner { bannerCopy := *banner - bannerCopy.W = openrtb.Uint64Ptr(w) - bannerCopy.H = openrtb.Uint64Ptr(h) + bannerCopy.W = openrtb2.Int64Ptr(w) + bannerCopy.H = openrtb2.Int64Ptr(h) return &bannerCopy } // parseImpressionObject parse the imp to get it ready to send to pubmatic -func parseImpressionObject(imp *openrtb.Imp, wrapExt *string, pubID *string) error { +func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) error { // PubMatic supports banner and video impressions. if imp.Banner == nil && imp.Video == nil { return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) @@ -537,7 +537,7 @@ func prepareImpressionExt(keywords map[string]string) string { return kvStr } -func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -552,7 +552,7 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 8ed2ccd391c..7e8f177a1ba 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" @@ -35,7 +35,7 @@ func TestJsonSamples(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. func CompareStringValue(val1 string, val2 string, t *testing.T) { if val1 != val2 { @@ -51,29 +51,29 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: breq.ID, BidID: "bidResponse_ID", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "pubmatic", - Bid: make([]openrtb.Bid, 0), + Bid: make([]openrtb2.Bid, 0), }, }, } rand.Seed(int64(time.Now().UnixNano())) - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { - bids = append(bids, openrtb.Bid{ + bids = append(bids, openrtb2.Bid{ ID: fmt.Sprintf("SeatID_%d", i), ImpID: imp.ID, Price: float64(int(rand.Float64()*1000)) / 100, @@ -130,7 +130,7 @@ func TestPubmaticTimeout(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -165,7 +165,7 @@ func TestPubmaticInvalidJson(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -201,7 +201,7 @@ func TestPubmaticInvalidStatusCode(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -234,7 +234,7 @@ func TestPubmaticInvalidInputParameters(t *testing.T) { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -306,7 +306,7 @@ func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -342,7 +342,7 @@ func TestPubmaticBasicResponse_AllParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -387,7 +387,7 @@ func TestPubmaticMultiImpressionResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -399,7 +399,7 @@ func TestPubmaticMultiImpressionResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 800, H: 200, @@ -435,7 +435,7 @@ func TestPubmaticMultiAdUnitResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -447,7 +447,7 @@ func TestPubmaticMultiAdUnitResponse(t *testing.T) { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 800, H: 200, @@ -484,7 +484,7 @@ func TestPubmaticMobileResponse(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -495,7 +495,7 @@ func TestPubmaticMobileResponse(t *testing.T) { }, } - pbReq.App = &openrtb.App{ + pbReq.App = &openrtb2.App{ ID: "com.test", Name: "testApp", } @@ -524,7 +524,7 @@ func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -556,7 +556,7 @@ func TestPubmaticAdSlotParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -636,7 +636,7 @@ func TestPubmaticSampleRequest(t *testing.T) { } pbReq.AdUnits[0] = pbs.AdUnit{ Code: "adUnit_1", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 100, H: 120, diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 9144d6ddaae..35bc7553db3 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -18,7 +18,7 @@ type PubnativeAdapter struct { URI string } -func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PubnativeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { impCount := len(request.Imp) requestData := make([]*adapters.RequestData, 0, impCount) errs := []error{} @@ -53,7 +53,7 @@ func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad continue } - requestCopy.Imp = []openrtb.Imp{imp} + requestCopy.Imp = []openrtb2.Imp{imp} reqJSON, err := json.Marshal(&requestCopy) if err != nil { errs = append(errs, err) @@ -77,7 +77,7 @@ func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad return requestData, errs } -func checkRequest(request *openrtb.BidRequest) error { +func checkRequest(request *openrtb2.BidRequest) error { if request.Device == nil || len(request.Device.OS) == 0 { return &errortypes.BadInput{ Message: "Impression is missing device OS information", @@ -87,7 +87,7 @@ func checkRequest(request *openrtb.BidRequest) error { return nil } -func convertImpression(imp *openrtb.Imp) error { +func convertImpression(imp *openrtb2.Imp) error { if imp.Banner == nil && imp.Video == nil && imp.Native == nil { return &errortypes.BadInput{ Message: "Pubnative only supports banner, video or native ads.", @@ -105,15 +105,15 @@ func convertImpression(imp *openrtb.Imp) error { } // make sure that banner has openrtb 2.3-compatible size information -func convertBanner(banner *openrtb.Banner) (*openrtb.Banner, error) { +func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { if len(banner.Format) > 0 { f := banner.Format[0] bannerCopy := *banner - bannerCopy.W = openrtb.Uint64Ptr(f.W) - bannerCopy.H = openrtb.Uint64Ptr(f.H) + bannerCopy.W = openrtb2.Int64Ptr(f.W) + bannerCopy.H = openrtb2.Int64Ptr(f.H) return &bannerCopy, nil } else { @@ -125,7 +125,7 @@ func convertBanner(banner *openrtb.Banner) (*openrtb.Banner, error) { return banner, nil } -func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -142,7 +142,7 @@ func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var parsedResponse openrtb.BidResponse + var parsedResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), @@ -173,7 +173,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index b07a2cba66f..35297d795a6 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,17 +1,16 @@ package pulsepoint import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "strconv" - - "bytes" - "context" - "io/ioutil" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -33,12 +32,12 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PulsePointAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error pubID := "" - imps := make([]openrtb.Imp, 0, len(request.Imp)) + imps := make([]openrtb2.Imp, 0, len(request.Imp)) for i := 0; i < len(request.Imp); i++ { imp := request.Imp[i] var bidderExt adapters.ExtImpBidder @@ -77,7 +76,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a publisher.ID = pubID site.Publisher = &publisher } else { - site.Publisher = &openrtb.Publisher{ID: pubID} + site.Publisher = &openrtb2.Publisher{ID: pubID} } request.Site = &site } else if request.App != nil { @@ -87,7 +86,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a publisher.ID = pubID app.Publisher = &publisher } else { - app.Publisher = &openrtb.Publisher{ID: pubID} + app.Publisher = &openrtb2.Publisher{ID: pubID} } request.App = &app } @@ -109,7 +108,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a }}, errs } -func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { // passback if response.StatusCode == http.StatusNoContent { return nil, nil @@ -127,14 +126,14 @@ func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern }} } // parse response - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) // map imps by id - impsByID := make(map[string]openrtb.Imp) + impsByID := make(map[string]openrtb2.Imp) for i := 0; i < len(internalRequest.Imp); i++ { impsByID[internalRequest.Imp[i].ID] = internalRequest.Imp[i] } @@ -156,7 +155,7 @@ func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, errs } -func getBidType(imp openrtb.Imp) openrtb_ext.BidType { +func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { // derive the bidtype purely from the impression itself if imp.Banner != nil { return openrtb_ext.BidTypeBanner @@ -235,7 +234,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde break } ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) - publisher := &openrtb.Publisher{ID: strconv.Itoa(params.PublisherId)} + publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} if ppReq.Site != nil { siteCopy := *ppReq.Site siteCopy.Publisher = publisher @@ -250,7 +249,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde if len(size) == 2 { width, err := strconv.Atoi(size[0]) if err == nil { - ppReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) + ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) } else { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Invalid Width param %s", size[0]), @@ -258,7 +257,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde } height, err := strconv.Atoi(size[1]) if err == nil { - ppReq.Imp[i].Banner.H = openrtb.Uint64Ptr(uint64(height)) + ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) } else { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Invalid Height param %s", size[1]), @@ -318,7 +317,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde debug.ResponseBody = string(body) } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index 33023d0500a..a16f9a0e470 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" @@ -187,7 +187,7 @@ func TestMobileAppRequest(t *testing.T) { server := service.Server ctx := context.TODO() req := SampleRequest(1, t) - req.App = &openrtb.App{ + req.App = &openrtb2.App{ ID: "com.facebook.katana", Name: "facebook", } @@ -215,7 +215,7 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { for i := 0; i < numberOfImpressions; i++ { req.AdUnits[i] = pbs.AdUnit{ Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -265,7 +265,7 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { */ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb.BidRequest + var lastBidRequest openrtb2.BidRequest server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -273,14 +273,14 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { http.Error(w, err.Error(), http.StatusInternalServerError) return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } lastBidRequest = breq - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { if tagsToBid[imp.TagID] { bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) @@ -290,9 +290,9 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { if len(bids) == 0 { w.WriteHeader(204) } else { - // serialize the bids to openrtb.BidResponse - js, _ := json.Marshal(openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Bid: bids, }, diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json index b5209ed4bbe..8d34bab0578 100644 --- a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json @@ -83,7 +83,7 @@ }], "expectedBidResponses": [], "expectedMakeBidsErrors": [{ - "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type uint64", + "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type int64", "comparison": "literal" } ] diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go index 5a34f3ef199..f3b24436d63 100644 --- a/adapters/revcontent/revcontent.go +++ b/adapters/revcontent/revcontent.go @@ -3,12 +3,13 @@ package revcontent import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb" + "net/http" + + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "net/http" ) type adapter struct { @@ -23,7 +24,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { reqBody, err := json.Marshal(request) if err != nil { @@ -46,7 +47,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return []*adapters.RequestData{req}, nil } -func checkRequest(request *openrtb.BidRequest) error { +func checkRequest(request *openrtb2.BidRequest) error { if (request.App == nil || len(request.App.Name) == 0) && (request.Site == nil || len(request.Site.Domain) == 0) { return &errortypes.BadInput{ Message: "Impression is missing app name or site domain, and must contain one.", @@ -57,7 +58,7 @@ func checkRequest(request *openrtb.BidRequest) error { } // MakeBids make the bids for the bid response. -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -74,7 +75,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/revcontent/revcontenttest/supplemental/bad_response.json b/adapters/revcontent/revcontenttest/supplemental/bad_response.json index bd562373f1b..751aed92c27 100644 --- a/adapters/revcontent/revcontenttest/supplemental/bad_response.json +++ b/adapters/revcontent/revcontenttest/supplemental/bad_response.json @@ -43,7 +43,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go index a507e778550..096e9190622 100644 --- a/adapters/rhythmone/rhythmone.go +++ b/adapters/rhythmone/rhythmone.go @@ -6,7 +6,7 @@ import ( "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type RhythmoneAdapter struct { endPoint string } -func (a *RhythmoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *RhythmoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var uri string @@ -43,7 +43,7 @@ func (a *RhythmoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad return nil, errs } -func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -59,7 +59,7 @@ func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %d. ", err), @@ -80,7 +80,7 @@ func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa return bidResponse, errs } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { @@ -103,7 +103,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *RhythmoneAdapter) preProcess(req *openrtb.BidRequest, errors []error) (*openrtb.BidRequest, string, []error) { +func (a *RhythmoneAdapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb2.BidRequest, string, []error) { numRequests := len(req.Imp) var uri string = "" for i := 0; i < numRequests; i++ { diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index 1f86d4f365b..b2eccd76305 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -27,7 +27,7 @@ type RTBHouseAdapter struct { // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (adapter *RTBHouseAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -57,7 +57,7 @@ const unexpectedStatusCodeFormat = "" + // MakeBids unpacks the server's response into Bids. func (adapter *RTBHouseAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -81,7 +81,7 @@ func (adapter *RTBHouseAdapter) MakeBids( return nil, []error{err} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json index b6af4209f48..f84f5555259 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json @@ -54,7 +54,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 32bf971cb6b..dff961cc79f 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -11,15 +11,13 @@ import ( "strings" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - - "golang.org/x/net/context/ctxhttp" - - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) const badvLimitSize = 50 @@ -326,7 +324,7 @@ func findPrimary(alt []int) (int, []int) { return primary, alt } -func parseRubiconSizes(sizes []openrtb.Format) (primary int, alt []int, err error) { +func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err error) { // Fixes #317 if len(sizes) < 1 { err = &errortypes.BadInput{ @@ -385,7 +383,7 @@ func (a *RubiconAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (res return } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { err = &errortypes.BadServerResponse{ @@ -523,14 +521,14 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * if rubiReq.Site != nil { siteCopy := *rubiReq.Site siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb.Publisher{} + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb.Content{} + siteCopy.Content = &openrtb2.Content{} siteCopy.Content.Language = rubiconUser.Language rubiReq.Site = &siteCopy } else { - site := &openrtb.Site{} - site.Content = &openrtb.Content{} + site := &openrtb2.Site{} + site.Content = &openrtb2.Content{} site.Content.Language = rubiconUser.Language rubiReq.Site = site } @@ -538,12 +536,12 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * if rubiReq.App != nil { appCopy := *rubiReq.App appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb.Publisher{} + appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiReq.App = &appCopy } - rubiReq.Imp = []openrtb.Imp{thisImp} + rubiReq.Imp = []openrtb2.Imp{thisImp} var reqBuffer bytes.Buffer err = json.NewEncoder(&reqBuffer).Encode(rubiReq) @@ -616,7 +614,7 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * return bids, nil } -func resolveVideoSizeId(placement openrtb.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { +func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { return 201, nil @@ -674,7 +672,7 @@ func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, } } -func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) var err error @@ -853,14 +851,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap if request.Site != nil { siteCopy := *request.Site siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb.Publisher{} + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.Site = &siteCopy } if request.App != nil { appCopy := *request.App appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb.Publisher{} + appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy } @@ -872,7 +870,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap } } - rubiconRequest.Imp = []openrtb.Imp{thisImp} + rubiconRequest.Imp = []openrtb2.Imp{thisImp} rubiconRequest.Cur = nil rubiconRequest.Ext = nil @@ -895,7 +893,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return requestData, errs } -func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb.Data) error { +func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { var segmentIdsToCopy = make([]string, 0) for _, dataRecord := range data { @@ -1018,7 +1016,7 @@ func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, rubiconUidsPar return nil } -func isVideo(imp openrtb.Imp) bool { +func isVideo(imp openrtb2.Imp) bool { video := imp.Video if video != nil { // Do any other media types exist? Or check required video fields. @@ -1027,12 +1025,12 @@ func isVideo(imp openrtb.Imp) bool { return false } -func isFullyPopulatedVideo(video *openrtb.Video) bool { +func isFullyPopulatedVideo(video *openrtb2.Video) bool { // These are just recommended video fields for XAPI return video.MIMEs != nil && video.Protocols != nil && video.MaxDuration != 0 && video.Linearity != 0 && video.API != nil } -func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -1049,14 +1047,14 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), }} } - var bidReq openrtb.BidRequest + var bidReq openrtb2.BidRequest if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { return nil, []error{err} } @@ -1103,7 +1101,7 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, nil } -func cmpOverrideFromBidRequest(bidRequest *openrtb.BidRequest) float64 { +func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { var bidRequestExt bidRequestExt if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { return 0 @@ -1112,7 +1110,7 @@ func cmpOverrideFromBidRequest(bidRequest *openrtb.BidRequest) float64 { return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride } -func mapImpIdToCpmOverride(imps []openrtb.Imp) map[string]float64 { +func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { impIdToCmpOverride := make(map[string]float64) for _, imp := range imps { var bidderExt adapters.ExtImpBidder diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 41e37f41126..747758bc820 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "github.com/prebid/prebid-server/errortypes" "io/ioutil" "net/http" "net/http/httptest" @@ -12,6 +11,9 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/pbs" @@ -21,7 +23,6 @@ import ( "strings" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -81,7 +82,7 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -126,14 +127,14 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 2), + Bid: make([]openrtb2.Bid, 2), }, }, } @@ -185,7 +186,7 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" rawTargeting := json.RawMessage(targeting) - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "random-id", ImpID: imp.ID, Price: rubidata.tags[ix].bid, @@ -320,8 +321,8 @@ func TestRubiconUserSyncInfo(t *testing.T) { assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") } -func getTestSizes() map[int]openrtb.Format { - return map[int]openrtb.Format{ +func getTestSizes() map[int]openrtb2.Format { + return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, 10: {W: 300, H: 600}, 2: {W: 728, H: 91}, @@ -335,7 +336,7 @@ func getTestSizes() map[int]openrtb.Format { func TestParseSizes(t *testing.T) { SIZE_ID := getTestSizes() - sizes := []openrtb.Format{ + sizes := []openrtb2.Format{ SIZE_ID[10], SIZE_ID[15], } @@ -345,7 +346,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 1, len(alt), "Alt not len 1") assert.Equal(t, 10, alt[0], "Alt not 10: %d", alt[0]) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ { W: 1111, H: 2222, @@ -357,7 +358,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 15, primary, "Primary %d != 15", primary) assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt)) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ SIZE_ID[15], } primary, alt, err = parseRubiconSizes(sizes) @@ -365,7 +366,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 15, primary, "Primary %d != 15", primary) assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt)) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ { W: 1111, H: 1222, @@ -385,20 +386,20 @@ func TestMASAlgorithm(t *testing.T) { ok bool } type testStub struct { - input []openrtb.Format + input []openrtb2.Format output output } testStubs := []testStub{ { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[2], SIZE_ID[9], }, output{2, []int{9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[9], SIZE_ID[15], @@ -406,14 +407,14 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[2], SIZE_ID[15], }, output{15, []int{2}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[15], SIZE_ID[9], SIZE_ID[2], @@ -421,7 +422,7 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{2, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[10], SIZE_ID[9], SIZE_ID[2], @@ -429,7 +430,7 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{10, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[15], @@ -437,7 +438,7 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[9], @@ -446,7 +447,7 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{33, 8, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[9], @@ -454,7 +455,7 @@ func TestMASAlgorithm(t *testing.T) { output{9, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[2], @@ -462,24 +463,24 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[2], }, output{2, []int{33}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[8], }, output{8, []int{}, false}, }, { - []openrtb.Format{}, + []openrtb2.Format{}, output{0, []int{}, true}, }, { - []openrtb.Format{ + []openrtb2.Format{ {W: 1111, H: 2345, }, @@ -527,7 +528,7 @@ func TestAppendTracker(t *testing.T) { func TestResolveVideoSizeId(t *testing.T) { testScenarios := []struct { - placement openrtb.VideoPlacementType + placement openrtb2.VideoPlacementType instl int8 impId string expected int @@ -626,11 +627,11 @@ func TestWrongFormatResponse(t *testing.T) { func TestZeroSeatBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{}, + SeatBid: []openrtb2.SeatBid{}, } js, _ := json.Marshal(resp) w.Write(js) @@ -647,14 +648,14 @@ func TestZeroSeatBidResponse(t *testing.T) { func TestEmptyBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 0), + Bid: make([]openrtb2.Bid, 0), }, }, } @@ -673,18 +674,18 @@ func TestEmptyBidResponse(t *testing.T) { func TestWrongBidIdResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 2), + Bid: make([]openrtb2.Bid, 2), }, }, } - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "random-id", ImpID: "zma", Price: 1.67, @@ -710,18 +711,18 @@ func TestWrongBidIdResponse(t *testing.T) { func TestZeroPriceBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 1), + Bid: make([]openrtb2.Bid, 1), }, }, } - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "test-bid-id", ImpID: "first-tag", Price: 0, @@ -747,7 +748,7 @@ func TestDifferentRequest(t *testing.T) { an, ctx, pbReq := CreatePrebidRequest(server, t) // test app not nil - pbReq.App = &openrtb.App{ + pbReq.App = &openrtb2.App{ ID: "com.test", Name: "testApp", } @@ -775,13 +776,13 @@ func TestDifferentRequest(t *testing.T) { pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) // test invalid size - pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ { W: 2222, H: 333, }, } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ { W: 222, H: 3333, @@ -795,7 +796,7 @@ func TestDifferentRequest(t *testing.T) { b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) assert.NotNil(t, err, "Should have gotten an error: %v", err) - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ { W: 222, H: 3333, @@ -861,7 +862,7 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap pbin := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb.Device{PxRatio: rubidata.devicePxRatio}, + Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, } @@ -869,7 +870,7 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ SIZE_ID[10], SIZE_ID[15], }, @@ -946,12 +947,12 @@ func TestOpenRTBRequest(t *testing.T) { devicePxRatio: 4.0, } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-banner-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -965,7 +966,7 @@ func TestOpenRTBRequest(t *testing.T) { }}`), }, { ID: "test-imp-video-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, @@ -988,10 +989,10 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, - User: &openrtb.User{ + User: &openrtb2.User{ Ext: json.RawMessage(`{"digitrust": { "id": "some-digitrust-id", "keyv": 1, @@ -1015,7 +1016,7 @@ func TestOpenRTBRequest(t *testing.T) { httpReq := reqs[i] assert.Equal(t, "POST", httpReq.Method, "Expected a POST message. Got %s", httpReq.Method) - var rpRequest openrtb.BidRequest + var rpRequest openrtb2.BidRequest if err := json.Unmarshal(httpReq.Body, &rpRequest); err != nil { t.Fatalf("Failed to unmarshal HTTP request: %v", rpRequest) } @@ -1031,16 +1032,16 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request from the outgoing request.") } - assert.Equal(t, uint64(300), rpRequest.Imp[0].Banner.Format[0].W, + assert.Equal(t, int64(300), rpRequest.Imp[0].Banner.Format[0].W, "Banner width does not match. Expected %d, Got %d", 300, rpRequest.Imp[0].Banner.Format[0].W) - assert.Equal(t, uint64(250), rpRequest.Imp[0].Banner.Format[0].H, + assert.Equal(t, int64(250), rpRequest.Imp[0].Banner.Format[0].H, "Banner height does not match. Expected %d, Got %d", 250, rpRequest.Imp[0].Banner.Format[0].H) - assert.Equal(t, uint64(300), rpRequest.Imp[0].Banner.Format[1].W, + assert.Equal(t, int64(300), rpRequest.Imp[0].Banner.Format[1].W, "Banner width does not match. Expected %d, Got %d", 300, rpRequest.Imp[0].Banner.Format[1].W) - assert.Equal(t, uint64(600), rpRequest.Imp[0].Banner.Format[1].H, + assert.Equal(t, int64(600), rpRequest.Imp[0].Banner.Format[1].H, "Banner height does not match. Expected %d, Got %d", 600, rpRequest.Imp[0].Banner.Format[1].H) } else if rpRequest.Imp[0].ID == "test-imp-video-id" { var rpExt rubiconVideoExt @@ -1048,10 +1049,10 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request from the outgoing request.") } - assert.Equal(t, uint64(640), rpRequest.Imp[0].Video.W, + assert.Equal(t, int64(640), rpRequest.Imp[0].Video.W, "Video width does not match. Expected %d, Got %d", 640, rpRequest.Imp[0].Video.W) - assert.Equal(t, uint64(360), rpRequest.Imp[0].Video.H, + assert.Equal(t, int64(360), rpRequest.Imp[0].Video.H, "Video height does not match. Expected %d, Got %d", 360, rpRequest.Imp[0].Video.H) assert.Equal(t, "video/mp4", rpRequest.Imp[0].Video.MIMEs[0], "Video MIMEs do not match. Expected %s, Got %s", "video/mp4", rpRequest.Imp[0].Video.MIMEs[0]) @@ -1084,17 +1085,17 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, @@ -1115,7 +1116,7 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { assert.Equal(t, 1, len(reqs), "Unexpected number of HTTP requests. Got %d. Expected %d", len(reqs), 1) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1131,12 +1132,12 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -1160,7 +1161,7 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1191,13 +1192,13 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { badvOverflowed[i] = strconv.Itoa(i) } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", BAdv: badvOverflowed, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], }, }, @@ -1215,7 +1216,7 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1228,12 +1229,12 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -1244,7 +1245,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, - User: &openrtb.User{ + User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { "source": "pubcid", @@ -1283,7 +1284,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1324,24 +1325,24 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, MaxDuration: 30, Linearity: 1, - API: []openrtb.APIFramework{}, + API: []openrtb2.APIFramework{}, }, Ext: json.RawMessage(`{"bidder": { "zoneId": 8394, @@ -1360,7 +1361,7 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t assert.Equal(t, 1, len(reqs), "Unexpected number of HTTP requests. Got %d. Expected %d", len(reqs), 1) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1376,18 +1377,18 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) { bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, MaxDuration: 30, Linearity: 1, - API: []openrtb.APIFramework{}, + API: []openrtb2.APIFramework{}, }, Ext: json.RawMessage(`{ "prebid":{ @@ -1401,7 +1402,7 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1439,12 +1440,12 @@ func TestOpenRTBSurpriseResponse(t *testing.T) { } func TestOpenRTBStandardResponse(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1486,12 +1487,12 @@ func TestOpenRTBStandardResponse(t *testing.T) { } func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1533,12 +1534,12 @@ func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { } func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1583,9 +1584,9 @@ func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { } func TestOpenRTBCopyBidIdFromResponseIfZero(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{}}, + Imp: []openrtb2.Imp{{}}, } requestJson, _ := json.Marshal(request) diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index 36af79c4534..c9b9726ff4e 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" @@ -33,7 +33,7 @@ type StrAdSeverParams struct { } type StrOpenRTBInterface interface { - requestFromOpenRTB(openrtb.Imp, *openrtb.BidRequest, string) (*adapters.RequestData, error) + requestFromOpenRTB(openrtb2.Imp, *openrtb2.BidRequest, string) (*adapters.RequestData, error) responseToOpenRTB([]byte, *adapters.RequestData) (*adapters.BidderResponse, []error) } @@ -70,7 +70,7 @@ type StrOpenRTBTranslator struct { UserAgentParsers UserAgentParsers } -func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openrtb.BidRequest, domain string) (*adapters.RequestData, error) { +func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *openrtb2.BidRequest, domain string) (*adapters.RequestData, error) { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -111,8 +111,8 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openr ConsentString: userInfo.Consent, USPrivacySignal: usPolicySignal, Iframe: strImpParams.Iframe, - Height: height, - Width: width, + Height: uint64(height), + Width: uint64(width), InstantPlayCapable: s.Util.canAutoPlayVideo(request.Device.UA, s.UserAgentParsers), TheTradeDeskUserId: userInfo.TtdUid, SharethroughUserId: userInfo.StxUid, @@ -152,7 +152,7 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap return nil, errs } - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ AdID: strResp.AdServerRequestID, ID: strResp.BidID, ImpID: btlrParams.BidID, @@ -161,8 +161,8 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap CrID: creative.Metadata.CreativeKey, DealID: creative.Metadata.DealID, AdM: adm, - H: btlrParams.Height, - W: btlrParams.Width, + H: int64(btlrParams.Height), + W: int64(btlrParams.Width), } typedBid.Bid = bid @@ -171,7 +171,7 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap return bidResponse, errs } -func (h StrBodyHelper) buildBody(request *openrtb.BidRequest, strImpParams openrtb_ext.ExtImpSharethrough) (body []byte, err error) { +func (h StrBodyHelper) buildBody(request *openrtb2.BidRequest, strImpParams openrtb_ext.ExtImpSharethrough) (body []byte, err error) { timeout := request.TMax if timeout == 0 { timeout = defaultTmax diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index 402e8365dd0..3b1f2159bb7 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" @@ -19,7 +19,7 @@ import ( type MockUtil struct { mockCanAutoPlayVideo func() bool mockGdprApplies func() bool - mockGetPlacementSize func() (uint64, uint64) + mockGetPlacementSize func() (int64, int64) mockParseUserInfo func() userInfo UtilityInterface } @@ -28,15 +28,15 @@ func (m MockUtil) canAutoPlayVideo(userAgent string, parsers UserAgentParsers) b return m.mockCanAutoPlayVideo() } -func (m MockUtil) gdprApplies(request *openrtb.BidRequest) bool { +func (m MockUtil) gdprApplies(request *openrtb2.BidRequest) bool { return m.mockGdprApplies() } -func (m MockUtil) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height uint64, width uint64) { +func (m MockUtil) getPlacementSize(imp openrtb2.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height, width int64) { return m.mockGetPlacementSize() } -func (m MockUtil) parseUserInfo(user *openrtb.User) (ui userInfo) { +func (m MockUtil) parseUserInfo(user *openrtb2.User) (ui userInfo) { return m.mockParseUserInfo() } @@ -75,26 +75,26 @@ func assertRequestDataEquals(t *testing.T, testName string, expected *adapters.R func TestSuccessRequestFromOpenRTB(t *testing.T) { tests := map[string]struct { - inputImp openrtb.Imp - inputReq *openrtb.BidRequest + inputImp openrtb2.Imp + inputReq *openrtb2.BidRequest inputDom string expected *adapters.RequestData }{ "Generates the correct AdServer request from Imp (no user provided)": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ ID: "abc", Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0} }`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }, - inputReq: &openrtb.BidRequest{ - App: &openrtb.App{Ext: []byte(`{}`)}, - Device: &openrtb.Device{ + inputReq: &openrtb2.BidRequest{ + App: &openrtb2.App{Ext: []byte(`{}`)}, + Device: &openrtb2.Device{ UA: "Android Chome/60", IP: "127.0.0.1", }, - Site: &openrtb.Site{Page: "http://a.domain.com/page"}, + Site: &openrtb2.Site{Page: "http://a.domain.com/page"}, BAdv: []string{"domain1.com", "domain2.com"}, TMax: 700, }, @@ -114,20 +114,20 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { }, }, "Generates width/height if not provided": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ ID: "abc", Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true} }`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }, - inputReq: &openrtb.BidRequest{ - App: &openrtb.App{Ext: []byte(`{}`)}, - Device: &openrtb.Device{ + inputReq: &openrtb2.BidRequest{ + App: &openrtb2.App{Ext: []byte(`{}`)}, + Device: &openrtb2.Device{ UA: "Android Chome/60", IP: "127.0.0.1", }, - Site: &openrtb.Site{Page: "http://a.domain.com/page"}, + Site: &openrtb2.Site{Page: "http://a.domain.com/page"}, BAdv: []string{"domain1.com", "domain2.com"}, TMax: 700, }, @@ -157,7 +157,7 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { mockUtil := MockUtil{ mockCanAutoPlayVideo: func() bool { return true }, mockGdprApplies: func() bool { return true }, - mockGetPlacementSize: func() (uint64, uint64) { return 100, 200 }, + mockGetPlacementSize: func() (int64, int64) { return 100, 200 }, mockParseUserInfo: func() userInfo { return userInfo{Consent: "ok", TtdUid: "ttduid", StxUid: "stxuid"} }, } @@ -177,27 +177,27 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { func TestFailureRequestFromOpenRTB(t *testing.T) { tests := map[string]struct { - inputImp openrtb.Imp - inputReq *openrtb.BidRequest + inputImp openrtb2.Imp + inputReq *openrtb2.BidRequest expectedError string }{ "Fails when unable to parse imp.Ext": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ Ext: []byte(`{"abc`), }, - inputReq: &openrtb.BidRequest{ - Device: &openrtb.Device{UA: "A", IP: "ip"}, - Site: &openrtb.Site{Page: "page"}, + inputReq: &openrtb2.BidRequest{ + Device: &openrtb2.Device{UA: "A", IP: "ip"}, + Site: &openrtb2.Site{Page: "page"}, }, expectedError: `unexpected end of JSON input`, }, "Fails when unable to parse imp.Ext.Bidder": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ Ext: []byte(`{ "bidder": "{ abc" }`), }, - inputReq: &openrtb.BidRequest{ - Device: &openrtb.Device{UA: "A", IP: "ip"}, - Site: &openrtb.Site{Page: "page"}, + inputReq: &openrtb2.BidRequest{ + Device: &openrtb2.Device{UA: "A", IP: "ip"}, + Site: &openrtb2.Site{Page: "page"}, }, expectedError: `json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpSharethrough`, }, @@ -212,7 +212,7 @@ func TestFailureRequestFromOpenRTB(t *testing.T) { mockUtil := MockUtil{ mockCanAutoPlayVideo: func() bool { return true }, mockGdprApplies: func() bool { return true }, - mockGetPlacementSize: func() (uint64, uint64) { return 100, 200 }, + mockGetPlacementSize: func() (int64, int64) { return 100, 200 }, mockParseUserInfo: func() userInfo { return userInfo{Consent: "ok", TtdUid: "ttduid", StxUid: "stxuid"} }, } @@ -288,7 +288,7 @@ func TestSuccessResponseToOpenRTB(t *testing.T) { expectedSuccess: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{{ BidType: openrtb_ext.BidTypeBanner, - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ AdID: "arid", ID: "bid", ImpID: "bidid", @@ -376,19 +376,19 @@ func TestFailResponseToOpenRTB(t *testing.T) { func TestBuildBody(t *testing.T) { tests := map[string]struct { - inputRequest *openrtb.BidRequest + inputRequest *openrtb2.BidRequest inputImp openrtb_ext.ExtImpSharethrough expectedJson []byte expectedError error }{ "Empty input: skips badomains, tmax default to 10 sec and sets deadline accordingly": { - inputRequest: &openrtb.BidRequest{}, + inputRequest: &openrtb2.BidRequest{}, inputImp: openrtb_ext.ExtImpSharethrough{}, expectedJson: []byte(`{"tmax":10000, "deadline":"2019-09-12T11:29:10.000123456Z"}`), expectedError: nil, }, "Sets badv as list of domains according to Badv (tmax default to 10 sec and sets deadline accordingly)": { - inputRequest: &openrtb.BidRequest{ + inputRequest: &openrtb2.BidRequest{ BAdv: []string{"dom1.com", "dom2.com"}, }, inputImp: openrtb_ext.ExtImpSharethrough{}, @@ -396,7 +396,7 @@ func TestBuildBody(t *testing.T) { expectedError: nil, }, "Sets tmax and deadline according to Tmax": { - inputRequest: &openrtb.BidRequest{ + inputRequest: &openrtb2.BidRequest{ TMax: 500, }, inputImp: openrtb_ext.ExtImpSharethrough{}, @@ -404,7 +404,7 @@ func TestBuildBody(t *testing.T) { expectedError: nil, }, "Sets bidfloor according to the Imp object": { - inputRequest: &openrtb.BidRequest{}, + inputRequest: &openrtb2.BidRequest{}, inputImp: openrtb_ext.ExtImpSharethrough{ BidFloor: 1.23, }, @@ -428,7 +428,7 @@ func TestBuildBody(t *testing.T) { func TestBuildUri(t *testing.T) { tests := map[string]struct { inputParams StrAdSeverParams - inputApp *openrtb.App + inputApp *openrtb2.App expected []string }{ "Generates expected URL, appending all params": { diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index a6ef4736fdf..65afb21adb7 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -5,7 +5,7 @@ import ( "net/http" "regexp" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -35,7 +35,7 @@ type SharethroughAdapter struct { AdServer StrOpenRTBInterface } -func (a SharethroughAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a SharethroughAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var reqs []*adapters.RequestData if request.Site == nil { @@ -56,7 +56,7 @@ func (a SharethroughAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * return reqs, []error{} } -func (a SharethroughAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a SharethroughAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index 9fca80e03ee..194894fd893 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -6,7 +6,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -21,7 +21,7 @@ type MockStrAdServer struct { StrOpenRTBInterface } -func (m MockStrAdServer) requestFromOpenRTB(imp openrtb.Imp, request *openrtb.BidRequest, domain string) (*adapters.RequestData, error) { +func (m MockStrAdServer) requestFromOpenRTB(imp openrtb2.Imp, request *openrtb2.BidRequest, domain string) (*adapters.RequestData, error) { return m.mockRequestFromOpenRTB() } @@ -88,22 +88,22 @@ func TestSuccessMakeRequests(t *testing.T) { } tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected []*adapters.RequestData }{ "Generates expected Request": { - input: &openrtb.BidRequest{ - Site: &openrtb.Site{ + input: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "test.com", }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "Android Chome/60", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "abc", Ext: []byte(`{"pkey": "pkey", "iframe": true, "iframeSize": [10, 20]}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }}, }, @@ -137,22 +137,22 @@ func TestSuccessMakeRequests(t *testing.T) { func TestFailureMakeRequests(t *testing.T) { tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected string }{ "Returns nil if failed to generate request": { - input: &openrtb.BidRequest{ - Site: &openrtb.Site{ + input: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "test.com", }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "Android Chome/60", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "abc", Ext: []byte(`{"pkey": "pkey", "iframe": true, "iframeSize": [10, 20]}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }}, }, @@ -216,7 +216,7 @@ func TestSuccessMakeBids(t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) - response, errors := adapter.MakeBids(&openrtb.BidRequest{}, &adapters.RequestData{}, test.inputResponse) + response, errors := adapter.MakeBids(&openrtb2.BidRequest{}, &adapters.RequestData{}, test.inputResponse) if len(errors) > 0 { t.Errorf("Expected no errors, got %d\n", len(errors)) } @@ -264,7 +264,7 @@ func TestFailureMakeBids(t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) - response, errors := adapter.MakeBids(&openrtb.BidRequest{}, &adapters.RequestData{}, test.inputResponse) + response, errors := adapter.MakeBids(&openrtb2.BidRequest{}, &adapters.RequestData{}, test.inputResponse) if response != nil { t.Errorf("Expected response to be nil, got %+v\n", response) } diff --git a/adapters/sharethrough/utils.go b/adapters/sharethrough/utils.go index 76d8b7a1f76..4f27e738b99 100644 --- a/adapters/sharethrough/utils.go +++ b/adapters/sharethrough/utils.go @@ -5,27 +5,28 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/openrtb_ext" "html/template" "net" "net/url" "regexp" "strconv" "time" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) const minChromeVersion = 53 const minSafariVersion = 10 type UtilityInterface interface { - gdprApplies(*openrtb.BidRequest) bool - parseUserInfo(*openrtb.User) userInfo + gdprApplies(*openrtb2.BidRequest) bool + parseUserInfo(*openrtb2.User) userInfo getAdMarkup([]byte, openrtb_ext.ExtImpSharethroughResponse, *StrAdSeverParams) (string, error) - getBestFormat([]openrtb.Format) (uint64, uint64) - getPlacementSize(openrtb.Imp, openrtb_ext.ExtImpSharethrough) (uint64, uint64) + getBestFormat([]openrtb2.Format) (int64, int64) + getPlacementSize(openrtb2.Imp, openrtb_ext.ExtImpSharethrough) (int64, int64) canAutoPlayVideo(string, UserAgentParsers) bool isAndroid(string) bool @@ -123,10 +124,10 @@ func (u Util) getAdMarkup(strRawResp []byte, strResp openrtb_ext.ExtImpSharethro return templatedBuf.String(), nil } -func (u Util) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height uint64, width uint64) { +func (u Util) getPlacementSize(imp openrtb2.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height, width int64) { height, width = 1, 1 if len(strImpParams.IframeSize) >= 2 { - height, width = uint64(strImpParams.IframeSize[0]), uint64(strImpParams.IframeSize[1]) + height, width = int64(strImpParams.IframeSize[0]), int64(strImpParams.IframeSize[1]) } else if imp.Banner != nil { height, width = u.getBestFormat(imp.Banner.Format) } @@ -134,7 +135,7 @@ func (u Util) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpS return } -func (u Util) getBestFormat(formats []openrtb.Format) (height uint64, width uint64) { +func (u Util) getBestFormat(formats []openrtb2.Format) (height, width int64) { height, width = 1, 1 for i := 0; i < len(formats); i++ { format := formats[i] @@ -195,7 +196,7 @@ func (u Util) isAtMinSafariVersion(userAgent string, parser *regexp.Regexp) bool return u.isAtMinVersion(userAgent, parser, minSafariVersion) } -func (u Util) gdprApplies(request *openrtb.BidRequest) bool { +func (u Util) gdprApplies(request *openrtb2.BidRequest) bool { var gdprApplies int64 if request.Regs != nil { @@ -208,7 +209,7 @@ func (u Util) gdprApplies(request *openrtb.BidRequest) bool { return gdprApplies != 0 } -func (u Util) parseUserInfo(user *openrtb.User) (ui userInfo) { +func (u Util) parseUserInfo(user *openrtb2.User) (ui userInfo) { if user == nil { return } diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index fffc52f6140..fb199369e59 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -4,11 +4,12 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "regexp" "testing" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestGetAdMarkup(t *testing.T) { @@ -71,31 +72,31 @@ func TestGetAdMarkup(t *testing.T) { func TestGetPlacementSize(t *testing.T) { tests := map[string]struct { - imp openrtb.Imp + imp openrtb2.Imp strImpParams openrtb_ext.ExtImpSharethrough - expectedHeight uint64 - expectedWidth uint64 + expectedHeight int64 + expectedWidth int64 }{ "Returns size from STR params if provided": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{IframeSize: []int{100, 200}}, expectedHeight: 100, expectedWidth: 200, }, "Skips size from STR params if malformed": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{IframeSize: []int{100}}, expectedHeight: 1, expectedWidth: 1, }, "Returns size from banner format if provided": { - imp: openrtb.Imp{Banner: &openrtb.Banner{Format: []openrtb.Format{{H: 100, W: 200}}}}, + imp: openrtb2.Imp{Banner: &openrtb2.Banner{Format: []openrtb2.Format{{H: 100, W: 200}}}}, strImpParams: openrtb_ext.ExtImpSharethrough{}, expectedHeight: 100, expectedWidth: 200, }, "Defaults to 1x1": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{}, expectedHeight: 1, expectedWidth: 1, @@ -114,22 +115,22 @@ func TestGetPlacementSize(t *testing.T) { func TestGetBestFormat(t *testing.T) { tests := map[string]struct { - input []openrtb.Format - expectedHeight uint64 - expectedWidth uint64 + input []openrtb2.Format + expectedHeight int64 + expectedWidth int64 }{ "Returns default size if empty input": { - input: []openrtb.Format{}, + input: []openrtb2.Format{}, expectedHeight: 1, expectedWidth: 1, }, "Returns size if only one is passed": { - input: []openrtb.Format{{H: 100, W: 100}}, + input: []openrtb2.Format{{H: 100, W: 100}}, expectedHeight: 100, expectedWidth: 100, }, "Returns biggest size if multiple are passed": { - input: []openrtb.Format{{H: 100, W: 100}, {H: 200, W: 200}, {H: 50, W: 50}}, + input: []openrtb2.Format{{H: 100, W: 100}, {H: 200, W: 200}, {H: 50, W: 50}}, expectedHeight: 200, expectedWidth: 200, }, @@ -344,27 +345,27 @@ func TestIsAtMinSafariVersion(t *testing.T) { } func TestGdprApplies(t *testing.T) { - bidRequestGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(`{"gdpr": 1}`), }, } - bidRequestNonGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestNonGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(`{"gdpr": 0}`), }, } - bidRequestEmptyGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestEmptyGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(``), }, } - bidRequestEmptyRegs := openrtb.BidRequest{ - Regs: &openrtb.Regs{}, + bidRequestEmptyRegs := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{}, } tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected bool }{ "Return true if gdpr set to 1": { @@ -396,7 +397,7 @@ func TestGdprApplies(t *testing.T) { func TestParseUserInfo(t *testing.T) { tests := map[string]struct { - input *openrtb.User + input *openrtb2.User expected userInfo }{ "Return empty strings if no User": { @@ -404,31 +405,31 @@ func TestParseUserInfo(t *testing.T) { expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return empty strings if no uids": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": []}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": []}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return empty strings if ID is not defined or empty string": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": null}]}, {"source": "adserver.org", "uids": [{"id": ""}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": null}]}, {"source": "adserver.org", "uids": [{"id": ""}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return consent correctly": { - input: &openrtb.User{Ext: []byte(`{ "consent": "abc" }`)}, + input: &openrtb2.User{Ext: []byte(`{ "consent": "abc" }`)}, expected: userInfo{Consent: "abc", TtdUid: "", StxUid: ""}, }, "Return ttd uid correctly": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "abc123", StxUid: ""}, }, "Ignore non-trade-desk uid": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "something", "uids": [{"id": "xyz"}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "something", "uids": [{"id": "xyz"}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Returns STX user id from buyer id": { - input: &openrtb.User{BuyerUID: "myid"}, + input: &openrtb2.User{BuyerUID: "myid"}, expected: userInfo{Consent: "", TtdUid: "", StxUid: "myid"}, }, "Full test": { - input: &openrtb.User{BuyerUID: "myid", Ext: []byte(`{ "consent": "abc", "eids": [{"source": "something", "uids": [{"id": "xyz"}]}, {"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, + input: &openrtb2.User{BuyerUID: "myid", Ext: []byte(`{ "consent": "abc", "eids": [{"source": "something", "uids": [{"id": "xyz"}]}, {"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, expected: userInfo{Consent: "abc", TtdUid: "abc123", StxUid: "myid"}, }, } diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index 2e31e51c0ad..47a762ecc04 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -31,7 +31,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -55,7 +55,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } func (a *SilverMobAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( []*adapters.RequestData, @@ -84,7 +84,7 @@ func (a *SilverMobAdapter) MakeRequests( continue } - requestCopy.Imp = []openrtb.Imp{imp} + requestCopy.Imp = []openrtb2.Imp{imp} reqJSON, err := json.Marshal(requestCopy) if err != nil { errs = append(errs, err) @@ -104,7 +104,7 @@ func (a *SilverMobAdapter) MakeRequests( return requestData, errs } -func (a *SilverMobAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSilverMob, error) { +func (a *SilverMobAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSilverMob, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -126,7 +126,7 @@ func (a *SilverMobAdapter) buildEndpointURL(params *openrtb_ext.ExtSilverMob) (s } func (a *SilverMobAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -149,7 +149,7 @@ func (a *SilverMobAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Error unmarshaling server Response: %s", err), @@ -176,7 +176,7 @@ func (a *SilverMobAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-response.json b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json index d2a1e890df0..4970678bef9 100644 --- a/adapters/silvermob/silvermobtest/supplemental/invalid-response.json +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json @@ -111,7 +111,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "Error unmarshaling server Response: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Error unmarshaling server Response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 29b4a5848bc..41229574f9e 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -58,7 +58,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SmaatoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) if len(request.Imp) == 0 { errs = append(errs, &errortypes.BadInput{Message: "no impressions in bid request"}) @@ -84,7 +84,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt if request.Site != nil { siteCopy := *request.Site - siteCopy.Publisher = &openrtb.Publisher{ID: publisherID} + siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} if request.Site.Ext != nil { var siteExt siteExt @@ -101,7 +101,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt if request.App != nil { appCopy := *request.App - appCopy.Publisher = &openrtb.Publisher{ID: publisherID} + appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} request.App = &appCopy } @@ -163,7 +163,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // MakeBids unpacks the server's response into Bids. -func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -178,7 +178,7 @@ func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return nil, []error{fmt.Errorf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -259,7 +259,7 @@ func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkup return "", fmt.Errorf("Invalid ad markup %s", adMarkup) } -func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { +func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W != nil && banner.H != nil { return banner, nil } @@ -267,16 +267,14 @@ func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { return banner, fmt.Errorf("No sizes provided for Banner %v", banner.Format) } bannerCopy := *banner - bannerCopy.W = new(uint64) - *bannerCopy.W = banner.Format[0].W - bannerCopy.H = new(uint64) - *bannerCopy.H = banner.Format[0].H + bannerCopy.W = openrtb2.Int64Ptr(banner.Format[0].W) + bannerCopy.H = openrtb2.Int64Ptr(banner.Format[0].H) return &bannerCopy, nil } // parseImpressionObject parse the imp to get it ready to send to smaato -func parseImpressionObject(imp *openrtb.Imp) error { +func parseImpressionObject(imp *openrtb2.Imp) error { adSpaceID, err := jsonparser.GetString(imp.Ext, "bidder", "adspaceId") if err != nil { return err @@ -303,7 +301,7 @@ func parseImpressionObject(imp *openrtb.Imp) error { return fmt.Errorf("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=%s", imp.ID) } -func extractUserExtAttributes(userExt userExt, userCopy *openrtb.User) { +func extractUserExtAttributes(userExt userExt, userCopy *openrtb2.User) { gender := userExt.Data.Gender if gender != "" { userCopy.Gender = gender diff --git a/adapters/smartadserver/smartadserver.go b/adapters/smartadserver/smartadserver.go index e2735d3bece..1e1eeac4f95 100644 --- a/adapters/smartadserver/smartadserver.go +++ b/adapters/smartadserver/smartadserver.go @@ -8,7 +8,7 @@ import ( "path" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -28,7 +28,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SmartAdserverAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ Message: "No impression in the bid request", @@ -43,7 +43,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // We create or copy the Site object. if smartRequest.Site == nil { - smartRequest.Site = &openrtb.Site{} + smartRequest.Site = &openrtb2.Site{} } else { site := *smartRequest.Site smartRequest.Site = &site @@ -51,7 +51,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // We create or copy the Publisher object. if smartRequest.Site.Publisher == nil { - smartRequest.Site.Publisher = &openrtb.Publisher{} + smartRequest.Site.Publisher = &openrtb2.Publisher{} } else { publisher := *smartRequest.Site.Publisher smartRequest.Site.Publisher = &publisher @@ -79,7 +79,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo smartRequest.Site.Publisher.ID = strconv.Itoa(smartadserverExt.NetworkID) // We send one request for each impression. - smartRequest.Imp = []openrtb.Imp{imp} + smartRequest.Imp = []openrtb2.Imp{imp} var errMarshal error if imp.Ext, errMarshal = json.Marshal(smartadserverExt); errMarshal != nil { @@ -117,7 +117,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo } // MakeBids unpacks the server's response into Bids. -func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -134,7 +134,7 @@ func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -169,7 +169,7 @@ func (a *SmartAdserverAdapter) BuildEndpointURL(params *openrtb_ext.ExtImpSmarta return uri.String(), nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Video != nil { diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index 4950f1cefb3..548d4f36db5 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -59,7 +59,7 @@ func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) { return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error { +func parseExtImp(dst *bidRequestExt, imp *openrtb2.Imp) error { var ext adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &ext); err != nil { return &errortypes.BadInput{ @@ -84,8 +84,8 @@ func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error { return nil } -func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var imps []openrtb.Imp +func (s *SmartRTBAdapter) MakeRequests(brq *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var imps []openrtb2.Imp var err error ext := bidRequestExt{} nrImps := len(brq.Imp) @@ -144,7 +144,7 @@ func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapter } func (s *SmartRTBAdapter) MakeBids( - brq *openrtb.BidRequest, drq *adapters.RequestData, + brq *openrtb2.BidRequest, drq *adapters.RequestData, rs *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { if rs.StatusCode == http.StatusNoContent { @@ -157,7 +157,7 @@ func (s *SmartRTBAdapter) MakeBids( }} } - var brs openrtb.BidResponse + var brs openrtb2.BidResponse if err := json.Unmarshal(rs.Body, &brs); err != nil { return nil, []error{err} } diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json index c56e7f21515..3d9f92df4a7 100644 --- a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json @@ -69,7 +69,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index ba727b4730c..e02ba4200f3 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -64,7 +64,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } func (a *SmartyAdsAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -106,7 +106,7 @@ func (a *SmartyAdsAdapter) MakeRequests( }}, nil } -func (a *SmartyAdsAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSmartyAds, error) { +func (a *SmartyAdsAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSmartyAds, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -154,7 +154,7 @@ func (a *SmartyAdsAdapter) CheckResponseStatusCodes(response *adapters.ResponseD } func (a *SmartyAdsAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -167,7 +167,7 @@ func (a *SmartyAdsAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -192,7 +192,7 @@ func (a *SmartyAdsAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/somoaudience/somoaudience.go b/adapters/somoaudience/somoaudience.go index 2bcb0282fe6..d799b5bb5f7 100644 --- a/adapters/somoaudience/somoaudience.go +++ b/adapters/somoaudience/somoaudience.go @@ -6,12 +6,11 @@ import ( "net/http" "strconv" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb" ) const hbconfig = "hb_pbs_1.0.0" @@ -24,12 +23,12 @@ type somoaudienceReqExt struct { BidderConfig string `json:"prebid"` } -func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SomoaudienceAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var bannerImps []openrtb.Imp - var videoImps []openrtb.Imp - var nativeImps []openrtb.Imp + var bannerImps []openrtb2.Imp + var videoImps []openrtb2.Imp + var nativeImps []openrtb2.Imp for _, imp := range request.Imp { if imp.Banner != nil { @@ -53,7 +52,7 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Somoaudience only supports single imp video request for _, videoImp := range videoImps { - reqCopy.Imp = []openrtb.Imp{videoImp} + reqCopy.Imp = []openrtb2.Imp{videoImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -63,7 +62,7 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Somoaudience only supports single imp video request for _, nativeImp := range nativeImps { - reqCopy.Imp = []openrtb.Imp{nativeImp} + reqCopy.Imp = []openrtb2.Imp{nativeImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -74,10 +73,10 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo } -func (a *SomoaudienceAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SomoaudienceAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error var err error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp reqExt := somoaudienceReqExt{BidderConfig: hbconfig} var placementHash string @@ -132,7 +131,7 @@ func (a *SomoaudienceAdapter) makeRequest(request *openrtb.BidRequest) (*adapter }, errs } -func preprocess(imp *openrtb.Imp, reqExt *somoaudienceReqExt) (string, error) { +func preprocess(imp *openrtb2.Imp, reqExt *somoaudienceReqExt) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", &errortypes.BadInput{ @@ -153,7 +152,7 @@ func preprocess(imp *openrtb.Imp, reqExt *somoaudienceReqExt) (string, error) { return somoExt.PlacementHash, nil } -func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -171,7 +170,7 @@ func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapt }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -190,7 +189,7 @@ func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapt return bidResponse, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index a13e9c67d9a..c3527a6a73e 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -30,7 +30,7 @@ type sonobiParams struct { } // MakeRequests Makes the OpenRTB request payload -func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var sonobiExt openrtb_ext.ExtImpSonobi var err error @@ -42,7 +42,7 @@ func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt for _, imp := range request.Imp { // Make a copy as we don't want to change the original request reqCopy := *request - reqCopy.Imp = append(make([]openrtb.Imp, 0, 1), imp) + reqCopy.Imp = append(make([]openrtb2.Imp, 0, 1), imp) var bidderExt adapters.ExtImpBidder if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { @@ -69,7 +69,7 @@ func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // makeRequest helper method to crete the http request data -func (a *SonobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SonobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -92,7 +92,7 @@ func (a *SonobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Requ } // MakeBids makes the bids -func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SonobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -111,7 +111,7 @@ func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -136,7 +136,7 @@ func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 9e904872903..fd2acc1fcbd 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -49,7 +49,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb return nil, err } - sovrnReq := openrtb.BidRequest{ + sovrnReq := openrtb2.BidRequest{ ID: sReq.ID, Imp: sReq.Imp, Site: sReq.Site, @@ -133,7 +133,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb debug.ResponseBody = responseBody } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ @@ -173,7 +173,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb return bids, nil } -func (s *SovrnAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) for i := 0; i < len(request.Imp); i++ { @@ -228,7 +228,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (s *SovrnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -245,7 +245,7 @@ func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), @@ -271,7 +271,7 @@ func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, nil } -func preprocess(imp *openrtb.Imp) (string, error) { +func preprocess(imp *openrtb2.Imp) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", &errortypes.BadInput{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index aa1b98f00f5..a7bd2a204b9 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" @@ -38,7 +38,7 @@ func TestJsonSamples(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. var testSovrnUserId = "SovrnUser123" var testUserAgent = "user-agent-test" @@ -144,12 +144,12 @@ func checkHttpRequest(req http.Request, t *testing.T) { func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { dnt := int8(0) - device := openrtb.Device{ + device := openrtb2.Device{ Language: "murican", DNT: &dnt, } - user := openrtb.User{ + user := openrtb2.User{ ID: testSovrnUserId, } @@ -165,7 +165,7 @@ func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { for i := 0; i < numberOfImpressions; i++ { req.AdUnits[i] = pbs.AdUnit{ Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 728, H: 90, @@ -251,7 +251,7 @@ func TestNotFoundResponse(t *testing.T) { func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb.BidRequest + var lastBidRequest openrtb2.BidRequest var lastHttpReq http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -262,23 +262,23 @@ func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService http.Error(w, err.Error(), http.StatusInternalServerError) return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } lastBidRequest = breq - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { if tagsToBid[imp.TagID] { bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) } } - // serialize the bids to openrtb.BidResponse - js, _ := json.Marshal(openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Bid: bids, }, diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index 7c9b4fab19f..f6ead542d2e 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -40,7 +40,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *SynacorMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SynacorMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var bidRequests []*adapters.RequestData @@ -53,9 +53,9 @@ func (a *SynacorMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return bidRequests, errs } -func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SynacorMediaAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp var re *ReqExt var firstExtImp *openrtb_ext.ExtImpSynacormedia = nil @@ -130,7 +130,7 @@ func (adapter *SynacorMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpS return macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{Host: params.SeatId}) } -func getExtImpObj(imp *openrtb.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { +func getExtImpObj(imp *openrtb2.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -149,7 +149,7 @@ func getExtImpObj(imp *openrtb.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { } // MakeBids make the bids for the bid response. -func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { const errorMessage string = "Unexpected status code: %d. Run with request.debug = 1 for more info" switch { case response.StatusCode == http.StatusNoContent: @@ -164,7 +164,7 @@ func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -187,7 +187,7 @@ func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json index 8e8b9a4d944..520f415bcf4 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json +++ b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json @@ -64,7 +64,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index de8cb49a471..abe2d90b8a6 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -9,7 +9,7 @@ import ( "text/template" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -37,7 +37,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *TappxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ Message: "No impression in the bid request", @@ -146,7 +146,7 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in return thisURI.String(), nil } -func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TappxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -161,7 +161,7 @@ func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -181,7 +181,7 @@ func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, []error{} } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index d99d4a98616..cda22e06937 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -35,7 +35,7 @@ func (a *TelariaAdapter) FetchEndpoint() string { } // Checker method to ensure len(request.Imp) > 0 -func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { +func (a *TelariaAdapter) CheckHasImps(request *openrtb2.BidRequest) error { if len(request.Imp) == 0 { err := &errortypes.BadInput{ Message: "Telaria: Missing Imp Object", @@ -46,7 +46,7 @@ func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { } // Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist -func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error { +func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb2.BidRequest) error { hasVideoObject := false for _, imp := range request.Imp { @@ -69,7 +69,7 @@ func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error } // Fetches the populated header object -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -97,7 +97,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } // Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format -func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpTelaria, error) { +func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTelaria, error) { var bidderExt adapters.ExtImpBidder err := json.Unmarshal(imp.Ext, &bidderExt) @@ -125,7 +125,7 @@ func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ex // Method to fetch the original publisher ID. Note that this method must be called // before we replace publisher.ID with seatCode -func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) string { +func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb2.BidRequest) string { if request.Site != nil && request.Site.Publisher != nil { return request.Site.Publisher.ID @@ -137,8 +137,8 @@ func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) s } // Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID -func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb.Publisher) *openrtb.Publisher { - var pub = &openrtb.Publisher{ID: seatCode} +func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb2.Publisher) *openrtb2.Publisher { + var pub = &openrtb2.Publisher{ID: seatCode} if publisher != nil { pub.Domain = publisher.Domain @@ -151,7 +151,7 @@ func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb } // This method changes .publisher.id to the seatCode -func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCode string) (*openrtb.Site, *openrtb.App) { +func (a *TelariaAdapter) PopulatePublisherId(request *openrtb2.BidRequest, seatCode string) (*openrtb2.Site, *openrtb2.App) { if request.Site != nil { siteCopy := *request.Site siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher) @@ -164,7 +164,7 @@ func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCo return nil, nil } -func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { // make a copy of the incoming request request := *requestIn @@ -263,7 +263,7 @@ func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseDat return nil } -func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { httpStatusError := a.CheckResponseStatusCodes(response) if httpStatusError != nil { @@ -272,7 +272,7 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR responseBody := response.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Telaria: Bad Server Response", diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 0b100094dbb..2e2b525ea50 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -32,7 +32,7 @@ func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { return openrtb_ext.BidTypeBanner } -func processImp(imp *openrtb.Imp) error { +func processImp(imp *openrtb2.Imp) error { // get the triplelift extension var ext adapters.ExtImpBidder var tlext openrtb_ext.ExtImpTriplelift @@ -56,13 +56,13 @@ func processImp(imp *openrtb.Imp) error { return nil } -func (a *TripleliftAdapter) MakeRequests(request *openrtb.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TripleliftAdapter) MakeRequests(request *openrtb2.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)+1) reqs := make([]*adapters.RequestData, 0, 1) // copy the request, because we are going to mutate it tlRequest := *request // this will contain all the valid impressions - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // pre-process the imps for _, imp := range tlRequest.Imp { if err := processImp(&imp); err == nil { @@ -94,7 +94,7 @@ func (a *TripleliftAdapter) MakeRequests(request *openrtb.BidRequest, extra *ada return reqs, errs } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -102,7 +102,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -116,7 +116,7 @@ func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern if response.StatusCode != http.StatusOK { return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 9936bf82283..3a4d0588e7a 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -37,7 +37,7 @@ func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { return openrtb_ext.BidTypeNative } -func processImp(imp *openrtb.Imp) error { +func processImp(imp *openrtb2.Imp) error { // get the triplelift extension var ext adapters.ExtImpBidder var tlext openrtb_ext.ExtImpTriplelift @@ -64,7 +64,7 @@ func processImp(imp *openrtb.Imp) error { } // Returns the effective publisher ID -func effectivePubID(pub *openrtb.Publisher) string { +func effectivePubID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher @@ -80,13 +80,13 @@ func effectivePubID(pub *openrtb.Publisher) string { return "unknown" } -func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb2.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)+1) reqs := make([]*adapters.RequestData, 0, 1) // copy the request, because we are going to mutate it tlRequest := *request // this will contain all the valid impressions - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // pre-process the imps for _, imp := range tlRequest.Imp { if err := processImp(&imp); err == nil { @@ -124,14 +124,14 @@ func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb.BidRequest, extr return reqs, errs } -func getPublisher(request *openrtb.BidRequest) *openrtb.Publisher { +func getPublisher(request *openrtb2.BidRequest) *openrtb2.Publisher { if request.App != nil { return request.App.Publisher } return request.Site.Publisher } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -139,7 +139,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -153,7 +153,7 @@ func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, if response.StatusCode != http.StatusOK { return nil, []error{&errortypes.BadServerResponse{Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index 8df715e38be..f1d4fa9f175 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -25,7 +25,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -43,12 +43,12 @@ func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } var errs []error - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } - var bidReq openrtb.BidRequest + var bidReq openrtb2.BidRequest if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { return nil, []error{err} } @@ -69,7 +69,7 @@ func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return bidResponse, errs } -func (a *UcfunnelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *UcfunnelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) // If all the requests were malformed, don't bother making a server call with no impressions. @@ -101,7 +101,7 @@ func (a *UcfunnelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada }}, errs } -func getPartnerId(request *openrtb.BidRequest) (string, []error) { +func getPartnerId(request *openrtb2.BidRequest) (string, []error) { var ext ExtBidderUcfunnel var errs = []error{} err := json.Unmarshal(request.Imp[0].Ext, &ext) @@ -125,7 +125,7 @@ func checkBidderParameter(ext ExtBidderUcfunnel) []error { return nil } -func getBidType(bidReq openrtb.BidRequest, impid string) openrtb_ext.BidType { +func getBidType(bidReq openrtb2.BidRequest, impid string) openrtb_ext.BidType { for i := range bidReq.Imp { if bidReq.Imp[i].ID == impid { if bidReq.Imp[i].Banner != nil { diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index 216c06dff7f..c735cb567ce 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -13,32 +13,32 @@ import ( func TestMakeRequests(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp3 := openrtb.Imp{ + imp3 := openrtb2.Imp{ ID: "1236", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, } - imp4 := openrtb.Imp{ + imp4 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - imp5 := openrtb.Imp{ + imp5 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - internalRequest01 := openrtb.BidRequest{Imp: []openrtb.Imp{}} - internalRequest02 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} - internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest01 := openrtb2.BidRequest{Imp: []openrtb2.Imp{}} + internalRequest02 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest03 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) @@ -54,12 +54,12 @@ func TestMakeRequests(t *testing.T) { } var testCases = []struct { - in []openrtb.BidRequest + in []openrtb2.BidRequest out1 [](int) out2 [](bool) }{ { - in: []openrtb.BidRequest{internalRequest01, internalRequest02, internalRequest03}, + in: []openrtb2.BidRequest{internalRequest01, internalRequest02, internalRequest03}, out1: [](int){1, 1, 0}, out2: [](bool){false, false, true}, }, @@ -76,31 +76,31 @@ func TestMakeRequests(t *testing.T) { } func TestMakeBids(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp3 := openrtb.Imp{ + imp3 := openrtb2.Imp{ ID: "1236", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, } - imp4 := openrtb.Imp{ + imp4 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - imp5 := openrtb.Imp{ + imp5 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} - internalRequest04 := openrtb.BidRequest{Imp: []openrtb.Imp{imp}} + internalRequest03 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest04 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp}} internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) @@ -126,14 +126,14 @@ func TestMakeBids(t *testing.T) { } var testCases = []struct { - in1 []openrtb.BidRequest + in1 []openrtb2.BidRequest in2 []adapters.RequestData in3 []adapters.ResponseData out1 [](bool) out2 [](bool) }{ { - in1: []openrtb.BidRequest{internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest04}, + in1: []openrtb2.BidRequest{internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest04}, in2: []adapters.RequestData{RequestData01, RequestData01, RequestData01, RequestData01, RequestData01, RequestData02}, in3: []adapters.ResponseData{mockResponse200, mockResponse203, mockResponse204, mockResponse400, mockResponseError, mockResponse200}, out1: [](bool){true, false, false, false, false, false}, diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index 52441e1a882..b9741c8c9e3 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -42,7 +42,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var extRegs openrtb_ext.ExtRegs if request.Regs != nil { if request.Regs.COPPA == 1 { @@ -69,11 +69,11 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapter return nil, []error{err} } - var modifiableSource openrtb.Source + var modifiableSource openrtb2.Source if request.Source != nil { modifiableSource = *request.Source } else { - modifiableSource = openrtb.Source{} + modifiableSource = openrtb2.Source{} } modifiableSource.Ext = setSourceExt() request.Source = &modifiableSource @@ -98,7 +98,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapter return []*adapters.RequestData{requestData}, nil } -func getHeaders(request *openrtb.BidRequest) http.Header { +func getHeaders(request *openrtb2.BidRequest) http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -121,7 +121,7 @@ func getHeaders(request *openrtb.BidRequest) http.Header { return headers } -func modifyImps(request *openrtb.BidRequest) error { +func modifyImps(request *openrtb2.BidRequest) error { for i := 0; i < len(request.Imp); i++ { imp := &request.Imp[i] @@ -157,7 +157,7 @@ func modifyImps(request *openrtb.BidRequest) error { return nil } -func getStoredRequestImpID(imp *openrtb.Imp) (string, error) { +func getStoredRequestImpID(imp *openrtb2.Imp) (string, error) { v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") if err != nil { @@ -171,7 +171,7 @@ func setSourceExt() json.RawMessage { return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) } -func setExt(request *openrtb.BidRequest) (json.RawMessage, error) { +func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") if err != nil { accountID = 0 @@ -195,7 +195,7 @@ func setExt(request *openrtb.BidRequest) (json.RawMessage, error) { } // MakeBids unpacks the server's response into Bids. -func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil @@ -215,7 +215,7 @@ func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.Re return nil, []error{err} } - var response openrtb.BidResponse + var response openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index ae0243aaf4e..871af6df46d 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -24,13 +24,13 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *UnrulyAdapter) ReplaceImp(imp openrtb.Imp, request *openrtb.BidRequest) *openrtb.BidRequest { +func (a *UnrulyAdapter) ReplaceImp(imp openrtb2.Imp, request *openrtb2.BidRequest) *openrtb2.BidRequest { reqCopy := *request - reqCopy.Imp = append(make([]openrtb.Imp, 0, 1), imp) + reqCopy.Imp = append(make([]openrtb2.Imp, 0, 1), imp) return &reqCopy } -func (a *UnrulyAdapter) BuildRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *UnrulyAdapter) BuildRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { reqJSON, err := json.Marshal(request) if err != nil { return nil, []error{err} @@ -52,7 +52,7 @@ func AddHeadersToRequest() http.Header { return headers } -func (a *UnrulyAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *UnrulyAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData for _, imp := range request.Imp { @@ -71,7 +71,7 @@ func (a *UnrulyAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt return adapterRequests, errs } -func getMediaTypeForImpWithId(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImpWithId(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { return openrtb_ext.BidTypeVideo, nil @@ -91,9 +91,9 @@ func CheckResponse(response *adapters.ResponseData) error { return nil } -func convertToAdapterBidResponse(response *adapters.ResponseData, internalRequest *openrtb.BidRequest) (*adapters.BidderResponse, []error) { +func convertToAdapterBidResponse(response *adapters.ResponseData, internalRequest *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { var errs []error - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -115,7 +115,7 @@ func convertToAdapterBidResponse(response *adapters.ResponseData, internalReques return bidResponse, errs } -func convertBidderNameInExt(imp *openrtb.Imp) (*openrtb.Imp, error) { +func convertBidderNameInExt(imp *openrtb2.Imp) (*openrtb2.Imp, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, err @@ -136,7 +136,7 @@ func convertBidderNameInExt(imp *openrtb.Imp) (*openrtb.Imp, error) { return imp, nil } -func (a *UnrulyAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *UnrulyAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if err := CheckResponse(response); err != nil { return nil, []error{err} } diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index 18a21b7dfd6..b7c9a28eb47 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -41,7 +41,7 @@ func TestReturnsNewUnrulyBidderWithParams(t *testing.T) { } func TestBuildRequest(t *testing.T) { - request := openrtb.BidRequest{} + request := openrtb2.BidRequest{} expectedJson, _ := json.Marshal(request) mockHeaders := http.Header{} mockHeaders.Add("Content-Type", "application/json;charset=utf-8") @@ -65,19 +65,19 @@ func TestBuildRequest(t *testing.T) { } func TestReplaceImp(t *testing.T) { - imp1 := openrtb.Imp{ID: "imp1"} - imp2 := openrtb.Imp{ID: "imp2"} - imp3 := openrtb.Imp{ID: "imp3"} - newImp := openrtb.Imp{ID: "imp4"} - request := openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}} + imp1 := openrtb2.Imp{ID: "imp1"} + imp2 := openrtb2.Imp{ID: "imp2"} + imp3 := openrtb2.Imp{ID: "imp3"} + newImp := openrtb2.Imp{ID: "imp4"} + request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} newRequest := adapter.ReplaceImp(newImp, &request) if len(newRequest.Imp) != 1 { t.Errorf("Size of Imp Array should be 1") } - if !reflect.DeepEqual(request, openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}}) { - t.Errorf("actual = %v expected = %v", request, openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}}) + if !reflect.DeepEqual(request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) { + t.Errorf("actual = %v expected = %v", request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) } if !reflect.DeepEqual(newImp, newRequest.Imp[0]) { t.Errorf("actual = %v expected = %v", newRequest.Imp[0], newImp) @@ -85,7 +85,7 @@ func TestReplaceImp(t *testing.T) { } func TestConvertBidderNameInExt(t *testing.T) { - imp := openrtb.Imp{Ext: json.RawMessage(`{"bidder": {"uuid": "1234", "siteid": "aSiteID"}}`)} + imp := openrtb2.Imp{Ext: json.RawMessage(`{"bidder": {"uuid": "1234", "siteid": "aSiteID"}}`)} actualImp, err := convertBidderNameInExt(&imp) @@ -112,17 +112,17 @@ func TestConvertBidderNameInExt(t *testing.T) { func TestMakeRequests(t *testing.T) { adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} - imp1 := openrtb.Imp{ID: "imp1", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - imp2 := openrtb.Imp{ID: "imp2", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - imp3 := openrtb.Imp{ID: "imp3", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid3", "siteid": "siteID3"}}`)} + imp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid1", "siteid": "siteID1"}}`)} + imp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid2", "siteid": "siteID2"}}`)} + imp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - expectImp1 := openrtb.Imp{ID: "imp1", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - expectImp2 := openrtb.Imp{ID: "imp2", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - expectImp3 := openrtb.Imp{ID: "imp3", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid3", "siteid": "siteID3"}}`)} + expectImp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid1", "siteid": "siteID1"}}`)} + expectImp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid2", "siteid": "siteID2"}}`)} + expectImp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - expectImps := []openrtb.Imp{expectImp1, expectImp2, expectImp3} + expectImps := []openrtb2.Imp{expectImp1, expectImp2, expectImp3} - inputRequest := openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}} + inputRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) mockHeaders := http.Header{} mockHeaders.Add("Content-Type", "application/json;charset=utf-8") @@ -132,7 +132,7 @@ func TestMakeRequests(t *testing.T) { t.Errorf("should have 3 imps") } for n, imp := range expectImps { - request := openrtb.BidRequest{Imp: []openrtb.Imp{imp}} + request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp}} expectedJson, _ := json.Marshal(request) data := adapters.RequestData{ Method: "POST", @@ -149,11 +149,11 @@ func TestMakeRequests(t *testing.T) { func TestGetMediaTypeForImpIsVideo(t *testing.T) { testID := string("4321") testBidMediaType := openrtb_ext.BidTypeVideo - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: testID, - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imps := []openrtb.Imp{imp} + imps := []openrtb2.Imp{imp} actual, _ := getMediaTypeForImpWithId(testID, imps) if actual != "video" { @@ -162,11 +162,11 @@ func TestGetMediaTypeForImpIsVideo(t *testing.T) { } func TestGetMediaTypeForImpWithNoIDPresent(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "4321", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imps := []openrtb.Imp{imp} + imps := []openrtb2.Imp{imp} _, err := getMediaTypeForImpWithId("1234", imps) expected := &errortypes.BadInput{ Message: fmt.Sprintf("Failed to find impression \"%s\" ", "1234"), @@ -177,26 +177,26 @@ func TestGetMediaTypeForImpWithNoIDPresent(t *testing.T) { } func TestConvertToAdapterBidResponseHasCorrectNumberOfBids(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } mockResponse := adapters.ResponseData{StatusCode: 200, Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} - internalRequest := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2}} + internalRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2}} mockBidResponse := adapters.NewBidderResponseWithBidsCapacity(5) typedBid := &adapters.TypedBid{ - Bid: &openrtb.Bid{ImpID: "1234"}, + Bid: &openrtb2.Bid{ImpID: "1234"}, BidType: "Video", } typedBid2 := &adapters.TypedBid{ - Bid: &openrtb.Bid{ImpID: "1235"}, + Bid: &openrtb2.Bid{ImpID: "1235"}, BidType: "Video", } diff --git a/adapters/valueimpression/valueimpression.go b/adapters/valueimpression/valueimpression.go index 5cbcf3e19b5..1397f6e020d 100644 --- a/adapters/valueimpression/valueimpression.go +++ b/adapters/valueimpression/valueimpression.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type ValueImpressionAdapter struct { endpoint string } -func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ValueImpressionAdapter) MakeRequests(request *openrtb2.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -36,7 +36,7 @@ func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unuse return adapterRequests, errs } -func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *ValueImpressionAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { var err error jsonBody, err := json.Marshal(request) @@ -55,7 +55,7 @@ func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adap }, nil } -func preprocess(request *openrtb.BidRequest) error { +func preprocess(request *openrtb2.BidRequest) error { if len(request.Imp) == 0 { return &errortypes.BadInput{ Message: "No Imps in Bid Request", @@ -85,7 +85,7 @@ func preprocess(request *openrtb.BidRequest) error { } // MakeBids based on valueimpression server response -func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb2.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil } @@ -102,7 +102,7 @@ func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json index c854548b78b..2de5213d686 100644 --- a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json @@ -59,7 +59,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type int64", "comparison": "regex" } ] diff --git a/adapters/verizonmedia/verizonmedia.go b/adapters/verizonmedia/verizonmedia.go index a721b67cf05..edbb05a3a62 100644 --- a/adapters/verizonmedia/verizonmedia.go +++ b/adapters/verizonmedia/verizonmedia.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,7 +17,7 @@ type VerizonMediaAdapter struct { URI string } -func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errors := make([]error, 0, 1) if len(request.Imp) == 0 { @@ -79,7 +79,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Split up multi-impression requests into multiple requests so that // each split request is only associated to a single impression reqCopy := *request - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} if request.Site != nil { siteCopy := *request.Site @@ -111,7 +111,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return reqs, errors } -func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -123,7 +123,7 @@ func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Bad server response: %d.", err), @@ -156,7 +156,7 @@ func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte return bidResponse, nil } -func getImpInfo(impId string, imps []openrtb.Imp) (bool, openrtb_ext.BidType) { +func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { var mediaType openrtb_ext.BidType var exists bool for _, imp := range imps { @@ -171,7 +171,7 @@ func getImpInfo(impId string, imps []openrtb.Imp) (bool, openrtb_ext.BidType) { return exists, mediaType } -func changeRequestForBidService(request *openrtb.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error { +func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error { /* Always override the tag ID and (site ID or app ID) of the request */ request.Imp[0].TagID = extension.Pos if request.Site != nil { @@ -198,10 +198,8 @@ func changeRequestForBidService(request *openrtb.BidRequest, extension *openrtb_ return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(uint64) - *banner.W = banner.Format[0].W - banner.H = new(uint64) - *banner.H = banner.Format[0].H + banner.W = openrtb2.Int64Ptr(banner.Format[0].W) + banner.H = openrtb2.Int64Ptr(banner.Format[0].H) return nil } diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index f5a21f2269e..1f1fae0a484 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -38,7 +38,7 @@ type visxResponse struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *VisxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VisxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) // copy the request, because we are going to mutate it @@ -65,7 +65,7 @@ func (a *VisxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapter } // MakeBids unpacks the server's response into Bids. -func (a *VisxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -91,14 +91,14 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequ for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bid := openrtb.Bid{} + bid := openrtb2.Bid{} bid.ID = internalRequest.ID bid.CrID = sb.Bid[i].CrID bid.ImpID = sb.Bid[i].ImpID bid.Price = sb.Bid[i].Price bid.AdM = sb.Bid[i].AdM - bid.W = sb.Bid[i].W - bid.H = sb.Bid[i].H + bid.W = int64(sb.Bid[i].W) + bid.H = int64(sb.Bid[i].H) bid.ADomain = sb.Bid[i].ADomain bid.DealID = sb.Bid[i].DealID diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index dadb03238ae..ff5bc2dc733 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -16,7 +16,7 @@ type VrtcalAdapter struct { endpoint string } -func (a *VrtcalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VrtcalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -42,7 +42,7 @@ func (a *VrtcalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // MakeBids make the bids for the bid response. -func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -60,7 +60,7 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index d603397b09f..74208ca4c30 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -8,7 +8,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -33,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData adapterRequest, errs := adapter.makeRequest(request) @@ -44,7 +44,7 @@ func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInf return adapterRequests, errs } -func (adapter *YeahmobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (adapter *YeahmobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error yeahmobiExt, errs := getYeahmobiExt(request) @@ -75,7 +75,7 @@ func (adapter *YeahmobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapt }, errs } -func transform(request *openrtb.BidRequest) { +func transform(request *openrtb2.BidRequest) { for i, imp := range request.Imp { if imp.Native != nil { var nativeRequest map[string]interface{} @@ -103,7 +103,7 @@ func transform(request *openrtb.BidRequest) { } } -func getYeahmobiExt(request *openrtb.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { +func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { var extImpYeahmobi openrtb_ext.ExtImpYeahmobi var errs []error @@ -131,7 +131,7 @@ func (adapter *YeahmobiAdapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (st } // MakeBids make the bids for the bid response. -func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -148,7 +148,7 @@ func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, external }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -169,7 +169,7 @@ func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } -func getBidType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json index 22939466309..0d77e5af93a 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json @@ -49,7 +49,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 086ce059898..447f8aa55fd 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -9,9 +9,9 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" "golang.org/x/text/currency" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -36,7 +36,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // Builds endpoint url based on adapter-specific pub settings from imp.ext -func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { +func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { uri, err := url.Parse(a.endpoint) if err != nil { return "", fmt.Errorf("failed to parse yieldlab endpoint: %v", err) @@ -86,7 +86,7 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openr return uri.String(), nil } -func (a *YieldlabAdapter) getGDPR(request *openrtb.BidRequest) (string, string, error) { +func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, error) { gdpr := "" var extRegs openrtb_ext.ExtRegs if request.Regs != nil { @@ -118,7 +118,7 @@ func (a *YieldlabAdapter) makeTargetingValues(params *openrtb_ext.ExtImpYieldlab return values.Encode() } -func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldlabAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{fmt.Errorf("invalid request %+v, no Impressions given", request)} } @@ -149,7 +149,7 @@ func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters. } // parseRequest extracts the Yieldlab request information from the request -func (a *YieldlabAdapter) parseRequest(request *openrtb.BidRequest) []*openrtb_ext.ExtImpYieldlab { +func (a *YieldlabAdapter) parseRequest(request *openrtb2.BidRequest) []*openrtb_ext.ExtImpYieldlab { params := make([]*openrtb_ext.ExtImpYieldlab, 0) for i := 0; i < len(request.Imp); i++ { @@ -187,7 +187,7 @@ func (a *YieldlabAdapter) mergeParams(params []*openrtb_ext.ExtImpYieldlab) *ope } // MakeBids make the bids for the bid response. -func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode != 200 { return nil, []error{ &errortypes.BadServerResponse{ @@ -226,14 +226,14 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } var bidType openrtb_ext.BidType - responseBid := &openrtb.Bid{ + responseBid := &openrtb2.Bid{ ID: strconv.FormatUint(bid.ID, 10), Price: float64(bid.Price) / 100, ImpID: internalRequest.Imp[i].ID, CrID: a.makeCreativeID(req, bid), DealID: strconv.FormatUint(bid.Pid, 10), - W: width, - H: height, + W: int64(width), + H: int64(height), } if internalRequest.Imp[i].Video != nil { @@ -268,11 +268,11 @@ func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtI return nil } -func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { +func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res)) } -func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { +func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { val := url.Values{} val.Set("ts", a.cacheBuster()) val.Set("id", ext.ExtId) diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 2d84444441e..6c9524a8823 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -20,7 +20,7 @@ type Ext struct { PlacementId string `json:"placement_id"` } -func (a *YieldmoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -33,7 +33,7 @@ func (a *YieldmoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return adapterRequests, errors } -func (a *YieldmoAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error if err := preprocess(request); err != nil { @@ -59,7 +59,7 @@ func (a *YieldmoAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req } // Mutate the request to get it ready to send to yieldmo. -func preprocess(request *openrtb.BidRequest) error { +func preprocess(request *openrtb2.BidRequest) error { for i := 0; i < len(request.Imp); i++ { var imp = request.Imp[i] var bidderExt adapters.ExtImpBidder @@ -95,7 +95,7 @@ func preprocess(request *openrtb.BidRequest) error { } // MakeBids make the bids for the bid response. -func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -112,7 +112,7 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -140,7 +140,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { //default to video unless banner exists in impression for _, imp := range imps { if imp.ID == impId && imp.Banner != nil { diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 5d7edd63018..813746e1d99 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -17,10 +17,10 @@ type YieldoneAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) - var validImps []openrtb.Imp + var validImps []openrtb2.Imp for i := 0; i < len(request.Imp); i++ { if err := preprocess(&request.Imp[i]); err == nil { validImps = append(validImps, request.Imp[i]) @@ -49,7 +49,7 @@ func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada } // MakeBids unpacks the server's response into Bids. -func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -66,7 +66,7 @@ func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, external }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -98,7 +98,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var ext adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &ext); err != nil { @@ -122,7 +122,7 @@ func preprocess(imp *openrtb.Imp) error { return nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/yieldone/yieldonetest/supplemental/bad_response.json b/adapters/yieldone/yieldonetest/supplemental/bad_response.json index fa993a2fff5..3112d1f7ba0 100644 --- a/adapters/yieldone/yieldonetest/supplemental/bad_response.json +++ b/adapters/yieldone/yieldonetest/supplemental/bad_response.json @@ -58,7 +58,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index 17fbba747df..ab1938fa518 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -19,7 +19,7 @@ type ZeroClickFraudAdapter struct { EndpointTemplate template.Template } -func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) headers := http.Header{ @@ -69,7 +69,7 @@ func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInf internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) */ func (a *ZeroClickFraudAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -88,7 +88,7 @@ func (a *ZeroClickFraudAdapter) MakeBids( }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -110,9 +110,9 @@ func (a *ZeroClickFraudAdapter) MakeBids( return bidResponse, nil } -func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp, error) { +func splitImpressions(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb2.Imp, error) { - var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp) + var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb2.Imp) for _, imp := range imps { bidderParams, err := getBidderParams(&imp) @@ -126,7 +126,7 @@ func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud] return m, nil } -func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { +func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -155,7 +155,7 @@ func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error return &zeroclickfraudExt, nil } -func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaType(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json index 84d6bd9d889..87ad168467d 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json @@ -81,7 +81,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/amp/parse.go b/amp/parse.go index 9e0e019f953..40604c774be 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" ) // Params defines the paramters of an AMP request. @@ -24,11 +24,11 @@ type Params struct { // Size defines size information of an AMP request. type Size struct { - Height uint64 - Multisize []openrtb.Format - OverrideHeight uint64 - OverrideWidth uint64 - Width uint64 + Height int64 + Multisize []openrtb2.Format + OverrideHeight int64 + OverrideWidth int64 + Width int64 } // ParseParams parses the AMP paramters from a HTTP request. @@ -67,26 +67,26 @@ func parseIntPtr(value string) *uint64 { return nil } -func parseInt(value string) uint64 { - if parsed, err := strconv.ParseUint(value, 10, 64); err == nil { +func parseInt(value string) int64 { + if parsed, err := strconv.ParseInt(value, 10, 64); err == nil { return parsed } return 0 } -func parseMultisize(multisize string) []openrtb.Format { +func parseMultisize(multisize string) []openrtb2.Format { if multisize == "" { return nil } sizeStrings := strings.Split(multisize, ",") - sizes := make([]openrtb.Format, 0, len(sizeStrings)) + sizes := make([]openrtb2.Format, 0, len(sizeStrings)) for _, sizeString := range sizeStrings { wh := strings.Split(sizeString, "x") if len(wh) != 2 { return nil } - f := openrtb.Format{ + f := openrtb2.Format{ W: parseInt(wh[0]), H: parseInt(wh[1]), } diff --git a/amp/parse_test.go b/amp/parse_test.go index e2c82d71030..3a15e81ab46 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) @@ -40,7 +40,7 @@ func TestParseParams(t *testing.T) { OverrideHeight: 3, OverrideWidth: 4, Width: 2, - Multisize: []openrtb.Format{ + Multisize: []openrtb2.Format{ {W: 10, H: 11}, {W: 12, H: 13}, }, }, @@ -96,7 +96,7 @@ func TestParseMultisize(t *testing.T) { testCases := []struct { description string multisize string - expectedFormats []openrtb.Format + expectedFormats []openrtb2.Format }{ { description: "Empty", @@ -106,18 +106,18 @@ func TestParseMultisize(t *testing.T) { { description: "One", multisize: "1x2", - expectedFormats: []openrtb.Format{{W: 1, H: 2}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}}, }, { description: "Many", multisize: "1x2,3x4", - expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 3, H: 4}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 3, H: 4}}, }, { // Existing Behavior: The " 3" token in the second size is parsed as 0. description: "Many With Space - Quirky Result", multisize: "1x2, 3x4", - expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 0, H: 4}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 0, H: 4}}, }, { description: "One - Zero Size - Ignored", diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 52edc79c2e3..1ec0c27e923 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -1,12 +1,13 @@ package config import ( - "github.com/stretchr/testify/assert" "net/http" "os" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/stretchr/testify/assert" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" ) @@ -19,8 +20,8 @@ func TestSampleModule(t *testing.T) { am.LogAuctionObject(&analytics.AuctionObject{ Status: http.StatusOK, Errors: nil, - Request: &openrtb.BidRequest{}, - Response: &openrtb.BidResponse{}, + Request: &openrtb2.BidRequest{}, + Response: &openrtb2.BidResponse{}, }) if count != 1 { t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") diff --git a/analytics/core.go b/analytics/core.go index 6837304541d..e9403968c27 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -3,7 +3,7 @@ package analytics import ( "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" @@ -30,8 +30,8 @@ type PBSAnalyticsModule interface { type AuctionObject struct { Status int Errors []error - Request *openrtb.BidRequest - Response *openrtb.BidResponse + Request *openrtb2.BidRequest + Response *openrtb2.BidResponse Account *config.Account StartTime time.Time } @@ -40,8 +40,8 @@ type AuctionObject struct { type AmpObject struct { Status int Errors []error - Request *openrtb.BidRequest - AuctionResponse *openrtb.BidResponse + Request *openrtb2.BidRequest + AuctionResponse *openrtb2.BidResponse AmpTargetingValues map[string]string Origin string StartTime time.Time @@ -51,8 +51,8 @@ type AmpObject struct { type VideoObject struct { Status int Errors []error - Request *openrtb.BidRequest - Response *openrtb.BidResponse + Request *openrtb2.BidRequest + Response *openrtb2.BidResponse VideoRequest *openrtb_ext.BidRequestVideo VideoResponse *openrtb_ext.BidResponseVideo StartTime time.Time diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index cfc923c70f6..cfb72f487b0 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -1,13 +1,14 @@ package filesystem import ( - "github.com/prebid/prebid-server/config" "net/http" "os" "strings" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/usersync" ) @@ -18,7 +19,7 @@ func TestAmpObject_ToJson(t *testing.T) { ao := &analytics.AmpObject{ Status: http.StatusOK, Errors: make([]error, 0), - AuctionResponse: &openrtb.BidResponse{}, + AuctionResponse: &openrtb2.BidResponse{}, AmpTargetingValues: map[string]string{}, } if aoJson := jsonifyAmpObject(ao); strings.Contains(aoJson, "Transactional Logs Error") { diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 4e36e8db2be..ba97d60da18 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -1,11 +1,12 @@ package helpers import ( - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/usersync" "net/http" "testing" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/usersync" ) func TestJsonifyAuctionObject(t *testing.T) { @@ -52,7 +53,7 @@ func TestJsonifyAmpObject(t *testing.T) { ao := &analytics.AmpObject{ Status: http.StatusOK, Errors: make([]error, 0), - AuctionResponse: &openrtb.BidResponse{}, + AuctionResponse: &openrtb2.BidResponse{}, AmpTargetingValues: map[string]string{}, } if _, err := JsonifyAmpObject(ao, "scopeId"); err != nil { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 6bebc937f5e..8cc572022ef 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) @@ -21,7 +21,7 @@ func loadJSONFromFile() (*analytics.AuctionObject, error) { } defer req.Close() - reqCtn := openrtb.BidRequest{} + reqCtn := openrtb2.BidRequest{} reqPayload, err := ioutil.ReadAll(req) if err != nil { return nil, err @@ -38,7 +38,7 @@ func loadJSONFromFile() (*analytics.AuctionObject, error) { } defer res.Close() - resCtn := openrtb.BidResponse{} + resCtn := openrtb2.BidResponse{} resPayload, err := ioutil.ReadAll(res) if err != nil { return nil, err diff --git a/endpoints/auction.go b/endpoints/auction.go index 92e9769d59e..069abfec4e5 100644 --- a/endpoints/auction.go +++ b/endpoints/auction.go @@ -307,8 +307,8 @@ func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, pr hbSize := "" if bid.Width != 0 && bid.Height != 0 { - width := strconv.FormatUint(bid.Width, 10) - height := strconv.FormatUint(bid.Height, 10) + width := strconv.FormatInt(bid.Width, 10) + height := strconv.FormatInt(bid.Height, 10) hbSize = width + "x" + height } diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index ed9a526d760..e99856129f7 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -10,7 +10,7 @@ import ( "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" @@ -518,7 +518,7 @@ func TestBidSizeValidate(t *testing.T) { AdUnits: []pbs.PBSAdUnit{ { BidID: "test_bidid1", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 350, H: 250, @@ -535,7 +535,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid2", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 100, H: 100, @@ -548,7 +548,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid3", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 200, H: 200, @@ -561,7 +561,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid_video", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 400, H: 400, @@ -574,7 +574,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid3", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 150, H: 150, @@ -587,7 +587,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid_y", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 150, H: 150, diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 1f3d622b80d..cc8df0bf1b2 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -14,7 +14,7 @@ import ( "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/amp" "github.com/prebid/prebid-server/analytics" @@ -289,7 +289,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // possible, it will return errors with messages that suggest improvements. // // If the errors list has at least one element, then no guarantees are made about the returned request. -func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { +func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { // Load the stored request for the AMP ID. req, e := deps.loadRequestJSONForAmp(httpRequest) if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { @@ -313,8 +313,8 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr } // Load the stored OpenRTB request for an incoming AMP request, or return the errors found. -func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { - req = &openrtb.BidRequest{} +func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { + req = &openrtb2.BidRequest{} errs = nil ampParams, err := amp.ParseParams(httpRequest) @@ -372,9 +372,9 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } -func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb.BidRequest) []error { +func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2.BidRequest) []error { if req.Site == nil { - req.Site = &openrtb.Site{} + req.Site = &openrtb2.Site{} } // Override the stored request sizes with AMP ones, if they exist. @@ -423,25 +423,25 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb. return nil } -func makeFormatReplacement(size amp.Size) []openrtb.Format { - var formats []openrtb.Format +func makeFormatReplacement(size amp.Size) []openrtb2.Format { + var formats []openrtb2.Format if size.OverrideWidth != 0 && size.OverrideHeight != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.OverrideWidth, H: size.OverrideHeight, }} } else if size.OverrideWidth != 0 && size.Height != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.OverrideWidth, H: size.Height, }} } else if size.Width != 0 && size.OverrideHeight != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.Width, H: size.OverrideHeight, }} } else if size.Width != 0 && size.Height != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.Width, H: size.Height, }} @@ -450,13 +450,13 @@ func makeFormatReplacement(size amp.Size) []openrtb.Format { return append(formats, size.Multisize...) } -func setWidths(formats []openrtb.Format, width uint64) { +func setWidths(formats []openrtb2.Format, width int64) { for i := 0; i < len(formats); i++ { formats[i].W = width } } -func setHeights(formats []openrtb.Format, height uint64) { +func setHeights(formats []openrtb2.Format, height int64) { for i := 0; i < len(formats); i++ { formats[i].H = height } @@ -464,7 +464,7 @@ func setHeights(formats []openrtb.Format, height uint64) { // AMP won't function unless ext.prebid.targeting and ext.prebid.cache.bids are defined. // If the user didn't include them, default those here. -func defaultRequestExt(req *openrtb.BidRequest) (errs []error) { +func defaultRequestExt(req *openrtb2.BidRequest) (errs []error) { errs = nil extRequest := &openrtb_ext.ExtRequest{} if req.Ext != nil && len(req.Ext) > 0 { @@ -506,7 +506,7 @@ func defaultRequestExt(req *openrtb.BidRequest) (errs []error) { return } -func setAmpExt(site *openrtb.Site, value string) { +func setAmpExt(site *openrtb2.Site, value string) { if len(site.Ext) > 0 { if _, dataType, _, _ := jsonparser.Get(site.Ext, "amp"); dataType == jsonparser.NotExist { if val, err := jsonparser.Set(site.Ext, []byte(value), "amp"); err == nil { @@ -538,16 +538,16 @@ func readPolicy(consent string) (privacy.PolicyWriter, error) { } // Sets the effective publisher ID for amp request -func setEffectiveAmpPubID(req *openrtb.BidRequest, account string) { - var pub *openrtb.Publisher +func setEffectiveAmpPubID(req *openrtb2.BidRequest, account string) { + var pub *openrtb2.Publisher if req.App != nil { if req.App.Publisher == nil { - req.App.Publisher = new(openrtb.Publisher) + req.App.Publisher = new(openrtb2.Publisher) } pub = req.App.Publisher } else if req.Site != nil { if req.Site.Publisher == nil { - req.Site.Publisher = new(openrtb.Publisher) + req.Site.Publisher = new(openrtb2.Publisher) } pub = req.Site.Publisher } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 77e76613c29..45bbb2dd3e5 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -11,10 +11,10 @@ import ( "strconv" "testing" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/mxmCherry/openrtb" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/exchange" @@ -189,7 +189,7 @@ func TestGDPRConsent(t *testing.T) { // Build Request bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -341,7 +341,7 @@ func TestCCPAConsent(t *testing.T) { // Build Request bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -446,7 +446,7 @@ func TestConsentWarnings(t *testing.T) { bid, err := getTestBidRequest(true, nil, testCase.regs == nil, testCase.regs) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -543,7 +543,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { // Build Request bid, err := getTestBidRequest(false, nil, true, nil) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -712,7 +712,7 @@ func TestAmpDebug(t *testing.T) { // Prevents #452 func TestAmpTargetingDefaults(t *testing.T) { - req := &openrtb.BidRequest{} + req := &openrtb2.BidRequest{} if errs := defaultRequestExt(req); len(errs) != 0 { t.Fatalf("Unexpected error defaulting request.ext for AMP: %v", errs) } @@ -797,7 +797,7 @@ func TestOverrideDimensions(t *testing.T) { formatOverrideSpec{ overrideWidth: 20, overrideHeight: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -808,7 +808,7 @@ func TestOverrideHeightNormalWidth(t *testing.T) { formatOverrideSpec{ width: 20, overrideHeight: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -819,7 +819,7 @@ func TestOverrideWidthNormalHeight(t *testing.T) { formatOverrideSpec{ overrideWidth: 20, height: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -829,7 +829,7 @@ func TestOverrideWidthNormalHeight(t *testing.T) { func TestMultisize(t *testing.T) { formatOverrideSpec{ multisize: "200x50,100x60", - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 200, H: 50, }, { @@ -844,7 +844,7 @@ func TestSizeWithMultisize(t *testing.T) { width: 20, height: 40, multisize: "200x50,100x60", - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }, { @@ -860,7 +860,7 @@ func TestSizeWithMultisize(t *testing.T) { func TestHeightOnly(t *testing.T) { formatOverrideSpec{ height: 200, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 300, H: 200, }}, @@ -870,7 +870,7 @@ func TestHeightOnly(t *testing.T) { func TestWidthOnly(t *testing.T) { formatOverrideSpec{ width: 150, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 150, H: 600, }}, @@ -884,7 +884,7 @@ type formatOverrideSpec struct { overrideHeight uint64 multisize string account string - expect []openrtb.Format + expect []openrtb2.Format } func (s formatOverrideSpec) execute(t *testing.T) { @@ -942,7 +942,7 @@ func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs } type mockAmpExchange struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest } var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ @@ -954,12 +954,12 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi }, } -func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - response := &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + response := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), }}, @@ -980,10 +980,10 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq type mockAmpExchangeWarnings struct{} -func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { - response := &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ +func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + response := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), }}, @@ -994,16 +994,16 @@ func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.Au } func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { - var width uint64 = 300 - var height uint64 = 300 - bidRequest := &openrtb.BidRequest{ + var width int64 = 300 + var height int64 = 300 + bidRequest := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "/19968336/header-bid-tag-0", Ext: json.RawMessage(`{"appnexus": { "placementId":12883451 }}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: width, H: 250, @@ -1018,7 +1018,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, }, }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "site-id", Page: "some-page", }, @@ -1034,7 +1034,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, } if !nilUser { - bidRequest.User = &openrtb.User{ + bidRequest.User = &openrtb2.User{ ID: "aUserId", BuyerUID: "aBuyerID", Ext: userExtData, @@ -1051,7 +1051,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, } if !nilRegs { - bidRequest.Regs = &openrtb.Regs{ + bidRequest.Regs = &openrtb2.Regs{ COPPA: 1, Ext: regsExtData, } @@ -1064,14 +1064,14 @@ func TestSetEffectiveAmpPubID(t *testing.T) { testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest account string expectedPubID string }{ { description: "No publisher ID provided", - req: &openrtb.BidRequest{ - App: &openrtb.App{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ Publisher: nil, }, }, @@ -1079,9 +1079,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in req.App.Publisher.ID", - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: testPubID, }, }, @@ -1090,9 +1090,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in req.Site.Publisher.ID", - req: &openrtb.BidRequest{ - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: testPubID, }, }, @@ -1101,9 +1101,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in account parameter", - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "", }, }, @@ -1113,9 +1113,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "req.Site.Publisher present but ID set to empty string", - req: &openrtb.BidRequest{ - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "", }, }, @@ -1206,21 +1206,21 @@ func TestBuildAmpObject(t *testing.T) { expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: nil, - Request: &openrtb.BidRequest{ + Request: &openrtb2.BidRequest{ ID: "some-request-id", - Device: &openrtb.Device{ + Device: &openrtb2.Device{ IP: "192.0.2.1", }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "prebid.org", - Publisher: &openrtb.Publisher{}, + Publisher: &openrtb2.Publisher{}, Ext: json.RawMessage(`{"amp":1}`), }, - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "some-impression-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 300, H: 250, @@ -1235,9 +1235,9 @@ func TestBuildAmpObject(t *testing.T) { TMax: 500, Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}`), }, - AuctionResponse: &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + AuctionResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), }}, diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 70d8e72629e..91acab33ea1 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -18,9 +18,9 @@ import ( "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb" - "github.com/mxmCherry/openrtb/native" - nativeRequests "github.com/mxmCherry/openrtb/native/request" + "github.com/mxmCherry/openrtb/v14/native1" + nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" + "github.com/mxmCherry/openrtb/v14/openrtb2" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" @@ -223,8 +223,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http // possible, it will return errors with messages that suggest improvements. // // If the errors list has at least one element, then no guarantees are made about the returned request. -func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { - req = &openrtb.BidRequest{} +func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { + req = &openrtb2.BidRequest{} errs = nil // Pull the request body into a buffer, so we have it for later usage. @@ -292,7 +292,7 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } -func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { +func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -362,6 +362,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return append(errL, err) } + if err := validateDevice(req.Device); err != nil { + return append(errL, err) + } + if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { @@ -397,9 +401,9 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return errL } -func validateAndFillSourceTID(req *openrtb.BidRequest) error { +func validateAndFillSourceTID(req *openrtb2.BidRequest) error { if req.Source == nil { - req.Source = &openrtb.Source{} + req.Source = &openrtb2.Source{} } if req.Source.TID == "" { if rawUUID, err := uuid.NewV4(); err == nil { @@ -475,7 +479,7 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde return nil } -func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error { +func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int) []error { if imp.ID == "" { return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} } @@ -492,12 +496,12 @@ func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]strin return []error{err} } - if imp.Video != nil && len(imp.Video.MIMEs) < 1 { - return []error{fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", index)} + if err := validateVideo(imp.Video, index); err != nil { + return []error{err} } - if imp.Audio != nil && len(imp.Audio.MIMEs) < 1 { - return []error{fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", index)} + if err := validateAudio(imp.Audio, index); err != nil { + return []error{err} } if err := fillAndValidateNative(imp.Native, index); err != nil { @@ -516,13 +520,22 @@ func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]strin return nil } -func validateBanner(banner *openrtb.Banner, impIndex int) error { +func validateBanner(banner *openrtb2.Banner, impIndex int) error { if banner == nil { return nil } - // Although these are only deprecated in the spec... since this is a new endpoint, we know nobody uses them yet. - // Let's start things off by pointing callers in the right direction. + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if banner.W != nil && *banner.W < 0 { + return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex) + } + if banner.H != nil && *banner.H < 0 { + return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex) + } + + // The following fields are deprecated in the OpenRTB 2.5 spec but are still present + // in the OpenRTB library we use. Enforce they are not specified. if banner.WMin != 0 { return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex) } @@ -541,16 +554,71 @@ func validateBanner(banner *openrtb.Banner, impIndex int) error { return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) } - for fmtIndex, format := range banner.Format { - if err := validateFormat(&format, impIndex, fmtIndex); err != nil { + for i, format := range banner.Format { + if err := validateFormat(&format, impIndex, i); err != nil { return err } } + + return nil +} + +func validateVideo(video *openrtb2.Video, impIndex int) error { + if video == nil { + return nil + } + + if len(video.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if video.W < 0 { + return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex) + } + if video.H < 0 { + return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex) + } + if video.MinBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex) + } + if video.MaxBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex) + } + + return nil +} + +func validateAudio(audio *openrtb2.Audio, impIndex int) error { + if audio == nil { + return nil + } + + if len(audio.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if audio.Sequence < 0 { + return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex) + } + if audio.MaxSeq < 0 { + return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex) + } + if audio.MinBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex) + } + if audio.MaxBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex) + } + return nil } // fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec. -func fillAndValidateNative(n *openrtb.Native, impIndex int) error { +func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { if n == nil { return nil } @@ -584,12 +652,12 @@ func fillAndValidateNative(n *openrtb.Native, impIndex int) error { return nil } -func validateNativeContextTypes(cType native.ContextType, cSubtype native.ContextSubType, impIndex int) error { +func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error { if cType == 0 { // Context is only recommended, so none is a valid type. return nil } - if cType < native.ContextTypeContent || cType > native.ContextTypeProduct { + if cType < native1.ContextTypeContent || cType > native1.ContextTypeProduct { return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } if cSubtype < 0 { @@ -602,20 +670,20 @@ func validateNativeContextTypes(cType native.ContextType, cSubtype native.Contex if cSubtype >= 500 { return fmt.Errorf("request.imp[%d].native.request.contextsubtype can't be greater than or equal to 500. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } - if cSubtype >= native.ContextSubTypeGeneral && cSubtype <= native.ContextSubTypeUserGenerated { - if cType != native.ContextTypeContent { + if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { + if cType != native1.ContextTypeContent { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } - if cSubtype >= native.ContextSubTypeSocial && cSubtype <= native.ContextSubTypeChat { - if cType != native.ContextTypeSocial { + if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { + if cType != native1.ContextTypeSocial { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } - if cSubtype >= native.ContextSubTypeSelling && cSubtype <= native.ContextSubTypeProductReview { - if cType != native.ContextTypeProduct { + if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { + if cType != native1.ContextTypeProduct { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil @@ -624,12 +692,12 @@ func validateNativeContextTypes(cType native.ContextType, cSubtype native.Contex return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } -func validateNativePlacementType(pt native.PlacementType, impIndex int) error { +func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { if pt == 0 { // Placement Type is only reccomended, not required. return nil } - if pt < native.PlacementTypeFeed || pt > native.PlacementTypeRecommendationWidget { + if pt < native1.PlacementTypeFeed || pt > native1.PlacementTypeRecommendationWidget { return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) } return nil @@ -686,7 +754,9 @@ func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex in return fmt.Errorf(assetErr, impIndex, assetIndex) } foundType = true - // It is technically valid to have neither w/h nor wmin/hmin, so no check + if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil { + return err + } } if asset.Video != nil { @@ -727,20 +797,20 @@ func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impInde func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error { if title.Len < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive integer", impIndex, assetIndex) + return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex) } return nil } func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { - if tracker.Event < native.EventTypeImpression || tracker.Event > native.EventTypeViewableVideo50 { + if tracker.Event < native1.EventTypeImpression || tracker.Event > native1.EventTypeViewableVideo50 { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } if len(tracker.Methods) < 1 { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } for methodIndex, method := range tracker.Methods { - if method < native.EventTrackingMethodImage || method > native.EventTrackingMethodJS { + if method < native1.EventTrackingMethodImage || method > native1.EventTrackingMethodJS { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) } } @@ -748,6 +818,22 @@ func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex in return nil } +func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error { + if img.W < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex) + } + if img.H < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex) + } + if img.WMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex) + } + if img.HMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex) + } + return nil +} + func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error { if len(video.MIMEs) < 1 { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex) @@ -766,14 +852,14 @@ func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIn } func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { - if data.Type < native.DataAssetTypeSponsored || data.Type > native.DataAssetTypeCTAText { + if data.Type < native1.DataAssetTypeSponsored || data.Type > native1.DataAssetTypeCTAText { return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) } return nil } -func validateNativeVideoProtocols(protocols []native.Protocol, impIndex int, assetIndex int) error { +func validateNativeVideoProtocols(protocols []native1.Protocol, impIndex int, assetIndex int) error { if len(protocols) < 1 { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) } @@ -785,16 +871,35 @@ func validateNativeVideoProtocols(protocols []native.Protocol, impIndex int, ass return nil } -func validateNativeVideoProtocol(protocol native.Protocol, impIndex int, assetIndex int, protocolIndex int) error { - if protocol < native.ProtocolVAST10 || protocol > native.ProtocolDAAST10Wrapper { +func validateNativeVideoProtocol(protocol native1.Protocol, impIndex int, assetIndex int, protocolIndex int) error { + if protocol < native1.ProtocolVAST10 || protocol > native1.ProtocolDAAST10Wrapper { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) } return nil } -func validateFormat(format *openrtb.Format, impIndex int, formatIndex int) error { +func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error { usesHW := format.W != 0 || format.H != 0 usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0 + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if format.W < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex) + } + if format.H < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex) + } + if format.WRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex) + } + if format.HRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex) + } + if format.WMin < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex) + } + if usesHW && usesRatios { return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex) } @@ -810,7 +915,7 @@ func validateFormat(format *openrtb.Format, impIndex int, formatIndex int) error return nil } -func validatePmp(pmp *openrtb.PMP, impIndex int) error { +func validatePmp(pmp *openrtb2.PMP, impIndex int) error { if pmp == nil { return nil } @@ -823,7 +928,7 @@ func validatePmp(pmp *openrtb.PMP, impIndex int) error { return nil } -func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]string, impIndex int) []error { +func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int) []error { errL := []error{} if len(imp.Ext) == 0 { return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} @@ -937,7 +1042,7 @@ func (deps *endpointDeps) validateAliases(aliases map[string]string) error { return nil } -func (deps *endpointDeps) validateSite(site *openrtb.Site) error { +func (deps *endpointDeps) validateSite(site *openrtb2.Site) error { if site == nil { return nil } @@ -955,7 +1060,7 @@ func (deps *endpointDeps) validateSite(site *openrtb.Site) error { return nil } -func (deps *endpointDeps) validateApp(app *openrtb.App) error { +func (deps *endpointDeps) validateApp(app *openrtb2.App) error { if app == nil { return nil } @@ -976,9 +1081,18 @@ func (deps *endpointDeps) validateApp(app *openrtb.App) error { return nil } -func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]string) error { - // DigiTrust support - if user != nil && user.Ext != nil { +func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]string) error { + if user == nil { + return nil + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if user.Geo != nil && user.Geo.Accuracy < 0 { + return errors.New("request.user.geo.accuracy must be a positive number") + } + + if user.Ext != nil { // Creating ExtUser object to check if DigiTrust is valid var userExt openrtb_ext.ExtUser if err := json.Unmarshal(user.Ext, &userExt); err == nil { @@ -1030,7 +1144,6 @@ func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]st } } } else { - // Return error. return fmt.Errorf("request.user.ext object is not valid: %v", err) } } @@ -1038,7 +1151,7 @@ func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]st return nil } -func validateRegs(regs *openrtb.Regs) error { +func validateRegs(regs *openrtb2.Regs) error { if regs != nil && len(regs.Ext) > 0 { var regsExt openrtb_ext.ExtRegs if err := json.Unmarshal(regs.Ext, ®sExt); err != nil { @@ -1051,7 +1164,30 @@ func validateRegs(regs *openrtb.Regs) error { return nil } -func sanitizeRequest(r *openrtb.BidRequest, ipValidator iputil.IPValidator) { +func validateDevice(device *openrtb2.Device) error { + if device == nil { + return nil + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if device.W < 0 { + return errors.New("request.device.w must be a positive number") + } + if device.H < 0 { + return errors.New("request.device.h must be a positive number") + } + if device.PPI < 0 { + return errors.New("request.device.ppi must be a positive number") + } + if device.Geo != nil && device.Geo.Accuracy < 0 { + return errors.New("request.device.geo.accuracy must be a positive number") + } + + return nil +} + +func sanitizeRequest(r *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if r.Device != nil { if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { r.Device.IP = "" @@ -1068,7 +1204,7 @@ func sanitizeRequest(r *openrtb.BidRequest, ipValidator iputil.IPValidator) { // OpenRTB properties from the headers and other implicit info. // // This function _should not_ override any fields which were defined explicitly by the caller in the request. -func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { sanitizeRequest(bidReq, deps.privateNetworkIPValidator) setDeviceImplicitly(httpReq, bidReq, deps.privateNetworkIPValidator) @@ -1083,7 +1219,7 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope } // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device -func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) { +func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidtor iputil.IPValidator) { setIPImplicitly(httpReq, bidReq, ipValidtor) setUAImplicitly(httpReq, bidReq) setDoNotTrackImplicitly(httpReq, bidReq) @@ -1092,7 +1228,7 @@ func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipVa // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, // since header bidding is generally a first-price auction. -func setAuctionTypeImplicitly(bidReq *openrtb.BidRequest) { +func setAuctionTypeImplicitly(bidReq *openrtb2.BidRequest) { if bidReq.AT == 0 { bidReq.AT = 1 } @@ -1100,13 +1236,13 @@ func setAuctionTypeImplicitly(bidReq *openrtb.BidRequest) { } // setSiteImplicitly uses implicit info from httpReq to populate bidReq.Site -func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Site == nil || bidReq.Site.Page == "" || bidReq.Site.Domain == "" { referrerCandidate := httpReq.Referer() if parsedUrl, err := url.Parse(referrerCandidate); err == nil { if domain, err := publicsuffix.EffectiveTLDPlusOne(parsedUrl.Host); err == nil { if bidReq.Site == nil { - bidReq.Site = &openrtb.Site{} + bidReq.Site = &openrtb2.Site{} } if bidReq.Site.Domain == "" { bidReq.Site.Domain = domain @@ -1125,7 +1261,7 @@ func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { } } -func setImpsImplicitly(httpReq *http.Request, imps []openrtb.Imp) { +func setImpsImplicitly(httpReq *http.Request, imps []openrtb2.Imp) { secure := int8(1) for i := 0; i < len(imps); i++ { if imps[i].Secure == nil && httputil.IsSecure(httpReq) { @@ -1285,18 +1421,18 @@ func getStoredRequestId(data []byte) (string, bool, error) { } // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. -func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidator iputil.IPValidator) { +func setIPImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if bidReq.Device == nil || (bidReq.Device.IP == "" && bidReq.Device.IPv6 == "") { if ip, ver := httputil.FindIP(httpReq, ipValidator); ip != nil { switch ver { case iputil.IPv4: if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.IP = ip.String() case iputil.IPv6: if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.IPv6 = ip.String() } @@ -1305,23 +1441,23 @@ func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValida } // setUAImplicitly sets the User Agent on bidReq, if it's not explicitly defined and it's defined on the request. -func setUAImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setUAImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Device == nil || bidReq.Device.UA == "" { if ua := httpReq.UserAgent(); ua != "" { if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.UA = ua } } } -func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Device == nil || bidReq.Device.DNT == nil { dnt := httpReq.Header.Get(dntKey) if dnt == "0" || dnt == "1" { if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } switch dnt { @@ -1368,7 +1504,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo } // Returns the account ID for the request -func getAccountID(pub *openrtb.Publisher) string { +func getAccountID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 19c58f05ae0..2deb5d1b762 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,11 +17,11 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/stored_requests" "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" - "github.com/mxmCherry/openrtb" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -152,8 +152,8 @@ func runTestCase(t *testing.T, fileData []byte, testFile string) { } if len(test.ExpectedBidResponse) > 0 { - var expectedBidResponse openrtb.BidResponse - var actualBidResponse openrtb.BidResponse + var expectedBidResponse openrtb2.BidResponse + var actualBidResponse openrtb2.BidResponse var err error err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) @@ -241,7 +241,7 @@ func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter { // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid` // arrays, if any, are not listed in the exact same order in the actual version and in the expected version. -func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb.BidResponse, actualBidResponse openrtb.BidResponse) { +func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb2.BidResponse, actualBidResponse openrtb2.BidResponse) { //Assert non-array BidResponse fields assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) @@ -252,12 +252,12 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile) //Given that bidResponses have the same length, compare them in an order-independent way using maps - var actualSeatBidsMap map[string]openrtb.SeatBid = make(map[string]openrtb.SeatBid, 0) + var actualSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) for _, seatBid := range actualBidResponse.SeatBid { actualSeatBidsMap[seatBid.Seat] = seatBid } - var expectedSeatBidsMap map[string]openrtb.SeatBid = make(map[string]openrtb.SeatBid, 0) + var expectedSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) for _, seatBid := range expectedBidResponse.SeatBid { expectedSeatBidsMap[seatBid.Seat] = seatBid } @@ -275,76 +275,76 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o } func TestBidRequestAssert(t *testing.T) { - appnexusB1 := openrtb.Bid{ID: "appnexus-bid-1", Price: 5.00} - appnexusB2 := openrtb.Bid{ID: "appnexus-bid-2", Price: 7.00} - rubiconB1 := openrtb.Bid{ID: "rubicon-bid-1", Price: 1.50} - rubiconB2 := openrtb.Bid{ID: "rubicon-bid-2", Price: 4.00} + appnexusB1 := openrtb2.Bid{ID: "appnexus-bid-1", Price: 5.00} + appnexusB2 := openrtb2.Bid{ID: "appnexus-bid-2", Price: 7.00} + rubiconB1 := openrtb2.Bid{ID: "rubicon-bid-1", Price: 1.50} + rubiconB2 := openrtb2.Bid{ID: "rubicon-bid-2", Price: 4.00} - sampleSeatBids := []openrtb.SeatBid{ + sampleSeatBids := []openrtb2.SeatBid{ { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB1, appnexusB2}, + Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, }, { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, } testSuites := []struct { description string - expectedBidResponse openrtb.BidResponse - actualBidResponse openrtb.BidResponse + expectedBidResponse openrtb2.BidResponse + actualBidResponse openrtb2.BidResponse }{ { "identical SeatBids, exact same SeatBid and Bid arrays order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, }, { "identical SeatBids but Seatbid array elements come in different order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB1, appnexusB2}, + Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, }, }, }, }, { "SeatBids seem to be identical except for the different order of Bid array elements in one of them", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB2, appnexusB1}, + Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, }, { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, }, }, }, { "Both SeatBid elements and bid elements come in different order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB2, rubiconB1}, + Bid: []openrtb2.Bid{rubiconB2, rubiconB1}, }, { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB2, appnexusB1}, + Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, }, }, }, @@ -626,7 +626,7 @@ func TestExchangeError(t *testing.T) { func TestUserAgentSetting(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setUAImplicitly(httpReq, bidReq) @@ -642,8 +642,8 @@ func TestUserAgentSetting(t *testing.T) { func TestUserAgentOverride(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb.BidRequest{ - Device: &openrtb.Device{ + bidReq := &openrtb2.BidRequest{ + Device: &openrtb2.Device{ UA: "bar", }, } @@ -656,7 +656,7 @@ func TestUserAgentOverride(t *testing.T) { } func TestAuctionTypeDefault(t *testing.T) { - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setAuctionTypeImplicitly(bidReq) if bidReq.AT != 1 { @@ -757,21 +757,21 @@ func TestImplicitDNT(t *testing.T) { testCases := []struct { description string dntHeader string - request openrtb.BidRequest - expectedRequest openrtb.BidRequest + request openrtb2.BidRequest + expectedRequest openrtb2.BidRequest }{ { description: "Device Missing - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{}, + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, }, { description: "Device Missing - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &disabled, }, }, @@ -779,9 +779,9 @@ func TestImplicitDNT(t *testing.T) { { description: "Device Missing - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -789,21 +789,21 @@ func TestImplicitDNT(t *testing.T) { { description: "Not Set In Request - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, }, { description: "Not Set In Request - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &disabled, }, }, @@ -811,11 +811,11 @@ func TestImplicitDNT(t *testing.T) { { description: "Not Set In Request - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -823,13 +823,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -837,13 +837,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -851,13 +851,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -946,7 +946,7 @@ func TestImplicitSecure(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https") - imps := []openrtb.Imp{ + imps := []openrtb2.Imp{ {}, {}, } @@ -961,7 +961,7 @@ func TestImplicitSecure(t *testing.T) { func TestRefererParsing(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("Referer", "http://test.mysite.com") - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setSiteImplicitly(httpReq, bidReq) @@ -1123,8 +1123,8 @@ func TestImplicitAMPNoExt(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{}, + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{}, } setSiteImplicitly(httpReq, &bidReq) assert.JSONEq(t, `{"amp":0}`, string(bidReq.Site.Ext)) @@ -1136,8 +1136,8 @@ func TestImplicitAMPOtherExt(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{ + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{ Ext: json.RawMessage(`{"other":true}`), }, } @@ -1151,8 +1151,8 @@ func TestExplicitAMP(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{ + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{ Ext: json.RawMessage(`{"amp":1}`), }, } @@ -1394,7 +1394,7 @@ func TestValidateImpExt(t *testing.T) { for _, group := range testGroups { for _, test := range group.testCases { - imp := &openrtb.Imp{Ext: test.impExt} + imp := &openrtb2.Imp{Ext: test.impExt} errs := deps.validateImpExt(imp, nil, 0) @@ -1438,20 +1438,20 @@ func TestCurrencyTrunc(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Cur: []string{"USD", "EUR"}, @@ -1482,23 +1482,23 @@ func TestCCPAInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`), }, } @@ -1532,23 +1532,23 @@ func TestNoSaleInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy": "1NYN"}`), }, Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), @@ -1583,20 +1583,20 @@ func TestValidateSourceTID(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, } @@ -1624,20 +1624,20 @@ func TestSChainInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), @@ -1662,12 +1662,12 @@ func TestGetAccountID(t *testing.T) { testCases := []struct { description string - pub *openrtb.Publisher + pub *openrtb2.Publisher expectedAccID string }{ { description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: testPubID, Ext: testPubExtJSON, }, @@ -1675,7 +1675,7 @@ func TestGetAccountID(t *testing.T) { }, { description: "Only Publisher.Ext.Prebid.ParentAccount present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: "", Ext: testPubExtJSON, }, @@ -1683,14 +1683,14 @@ func TestGetAccountID(t *testing.T) { }, { description: "Only Publisher.ID present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: testPubID, }, expectedAccID: testPubID, }, { description: "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present", - pub: &openrtb.Publisher{}, + pub: &openrtb2.Publisher{}, expectedAccID: metrics.PublisherUnknown, }, { @@ -1709,15 +1709,15 @@ func TestGetAccountID(t *testing.T) { func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest ipValidator iputil.IPValidator expectedIPv4 string expectedIPv6 string }{ { description: "Empty", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "", IPv6: "", }, @@ -1727,8 +1727,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Valid", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1.1.1.1", IPv6: "1111::", }, @@ -1739,8 +1739,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Invalid", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1.1.1.1", IPv6: "1111::", }, @@ -1751,8 +1751,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Invalid - Wrong IP Types", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1111::", IPv6: "1.1.1.1", }, @@ -1763,8 +1763,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Malformed", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "malformed", IPv6: "malformed", }, @@ -1785,26 +1785,26 @@ func TestValidateAndFillSourceTID(t *testing.T) { testTID := "some-tid" testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest expectRandTID bool expectedTID string }{ { description: "req.Source not present. Expecting a randomly generated TID value", - req: &openrtb.BidRequest{}, + req: &openrtb2.BidRequest{}, expectRandTID: true, }, { description: "req.Source.TID not present. Expecting a randomly generated TID value", - req: &openrtb.BidRequest{ - Source: &openrtb.Source{}, + req: &openrtb2.BidRequest{ + Source: &openrtb2.Source{}, }, expectRandTID: true, }, { description: "req.Source.TID present. Expecting no change", - req: &openrtb.BidRequest{ - Source: &openrtb.Source{ + req: &openrtb2.BidRequest{ + Source: &openrtb2.Source{ TID: testTID, }, }, @@ -1843,20 +1843,20 @@ func TestEidPermissionsInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), @@ -2150,39 +2150,39 @@ type warningsCheckExchange struct { auctionRequest exchange.AuctionRequest } -func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { e.auctionRequest = r return nil, nil } // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { - gotRequest *openrtb.BidRequest + gotRequest *openrtb2.BidRequest } -func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { e.gotRequest = r.BidRequest - return &openrtb.BidResponse{ + return &openrtb2.BidResponse{ ID: r.BidRequest.ID, BidID: "test bid id", - NBR: openrtb.NoBidReasonCodeUnknownError.Ptr(), + NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), }, nil } type mockBidExchange struct { - gotRequest *openrtb.BidRequest + gotRequest *openrtb2.BidRequest } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext // into the bidResponse.Ext to assert the bidder adapters that were not filtered out in the validation process -func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { - bidResponse := &openrtb.BidResponse{ +func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + bidResponse := &openrtb2.BidResponse{ ID: r.BidRequest.ID, BidID: "test bid id", - NBR: openrtb.NoBidReasonCodeUnknownError.Ptr(), + NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), } if len(r.BidRequest.Imp) > 0 { - var SeatBidMap = make(map[string]openrtb.SeatBid, 0) + var SeatBidMap = make(map[string]openrtb2.SeatBid, 0) for _, imp := range r.BidRequest.Imp { var bidderExts map[string]json.RawMessage if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { @@ -2205,9 +2205,9 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq for bidderNameOrAlias := range bidderExts { if isBidderToValidate(bidderNameOrAlias) { if val, ok := SeatBidMap[bidderNameOrAlias]; ok { - val.Bid = append(val.Bid, openrtb.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) + val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) } else { - SeatBidMap[bidderNameOrAlias] = openrtb.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} + SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} } } } @@ -2222,7 +2222,7 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq type brokenExchange struct{} -func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { return nil, errors.New("Critical, unrecoverable error.") } @@ -2585,14 +2585,14 @@ func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) } type mockExchange struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest } -func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", }}, }}, diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index b42945488cc..67f9bc37b28 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -4,13 +4,13 @@ import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" ) -func processInterstitials(req *openrtb.BidRequest) error { +func processInterstitials(req *openrtb2.BidRequest) error { var devExt openrtb_ext.ExtDevice unmarshalled := true for i := range req.Imp { @@ -38,8 +38,8 @@ func processInterstitials(req *openrtb.BidRequest) error { return nil } -func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb.Device) error { - var maxWidth, maxHeight, minWidth, minHeight uint64 +func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb2.Device) error { + var maxWidth, maxHeight, minWidth, minHeight int64 if imp.Banner == nil { // custom interstitial support is only available for banner requests. return nil @@ -56,8 +56,8 @@ func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, maxWidth = device.W maxHeight = device.H } - minWidth = (maxWidth * devExt.Prebid.Interstitial.MinWidthPerc) / 100 - minHeight = (maxHeight * devExt.Prebid.Interstitial.MinHeightPerc) / 100 + minWidth = (maxWidth * int64(devExt.Prebid.Interstitial.MinWidthPerc)) / 100 + minHeight = (maxHeight * int64(devExt.Prebid.Interstitial.MinHeightPerc)) / 100 imp.Banner.Format = genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight) if len(imp.Banner.Format) == 0 { return &errortypes.BadInput{Message: fmt.Sprintf("Unable to set interstitial size list for Imp id=%s (No valid sizes between %dx%d and %dx%d)", imp.ID, minWidth, minHeight, maxWidth, maxHeight)} @@ -65,10 +65,10 @@ func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, return nil } -func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight uint64) []openrtb.Format { +func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight int64) []openrtb2.Format { sizes := make([]config.InterstitialSize, 0, 10) for _, size := range config.ResolvedInterstitialSizes { - if size.Width >= minWidth && size.Width <= maxWidth && size.Height >= minHeight && size.Height <= maxHeight { + if int64(size.Width) >= minWidth && int64(size.Width) <= maxWidth && int64(size.Height) >= minHeight && int64(size.Height) <= maxHeight { sizes = append(sizes, size) if len(sizes) >= 10 { // we have enough sizes @@ -76,9 +76,9 @@ func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight uint64) []op } } } - formatList := make([]openrtb.Format, 0, len(sizes)) + formatList := make([]openrtb2.Format, 0, len(sizes)) for _, size := range sizes { - formatList = append(formatList, openrtb.Format{W: size.Width, H: size.Height}) + formatList = append(formatList, openrtb2.Format{W: int64(size.Width), H: int64(size.Height)}) } return formatList } diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 1c6eb2555db..7691a6672e0 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -4,17 +4,17 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) -var request = &openrtb.BidRequest{ +var request = &openrtb2.BidRequest{ ID: "some-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "my-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 300, H: 600, @@ -25,7 +25,7 @@ var request = &openrtb.BidRequest{ Ext: json.RawMessage(`{"appnexus": {"placementId": 12883451}}`), }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ H: 640, W: 320, Ext: json.RawMessage(`{"prebid": {"interstitial": {"minwidthperc": 60, "minheightperc": 60}}}`), @@ -37,7 +37,7 @@ func TestInterstitial(t *testing.T) { if err := processInterstitials(myRequest); err != nil { t.Fatalf("Error processing interstitials: %v", err) } - targetFormat := []openrtb.Format{ + targetFormat := []openrtb2.Format{ { W: 300, H: 600, diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json new file mode 100644 index 00000000000..62a96050121 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio max bitrate.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "maxbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.maxbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json new file mode 100644 index 00000000000..5eb48e0c396 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio max sequence.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "maxseq": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.maxseq must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json new file mode 100644 index 00000000000..304b77e2165 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio min bitrate.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "minbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.minbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json new file mode 100644 index 00000000000..2c1e181d96b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio sequence.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "sequence": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.sequence must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json new file mode 100644 index 00000000000..b4ab8eefadb --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json @@ -0,0 +1,23 @@ +{ + "description": "Request has a negative banner height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": -1 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json new file mode 100644 index 00000000000..968cc366b8f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json @@ -0,0 +1,23 @@ +{ + "description": "Request has a negative banner width.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": -1, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json new file mode 100644 index 00000000000..ce9cf24a860 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json @@ -0,0 +1,30 @@ +{ + "description": "Request has a negative device geography accuracy.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": 50, + "geo": { + "accuracy": -1 + } + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.geo.accuracy must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json new file mode 100644 index 00000000000..12a0b6cb662 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json @@ -0,0 +1,27 @@ +{ + "description": "Request has a negative device height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": -1 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json new file mode 100644 index 00000000000..8ca2c03c198 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative PPI height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": 50, + "ppi": -1 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.ppi must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json new file mode 100644 index 00000000000..f3c17cee1f8 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json @@ -0,0 +1,27 @@ +{ + "description": "Request has a negative device width.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": -1, + "h": 50 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json new file mode 100644 index 00000000000..3d47c9a00ec --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json @@ -0,0 +1,20 @@ +{ + "description": "Request has a negative banner format height.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json new file mode 100644 index 00000000000..93832f566ae --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format height ratio.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "hratio": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].hratio must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json new file mode 100644 index 00000000000..7e845c63bcd --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json @@ -0,0 +1,20 @@ +{ + "description": "Request has a negative banner format width.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": -1, + "h": 50 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json new file mode 100644 index 00000000000..ed618c0f459 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format width minimum.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "wmin": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].wmin must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json new file mode 100644 index 00000000000..84c72b47d69 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format width ratio.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "wratio": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].wratio must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json new file mode 100644 index 00000000000..163a88eeb79 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative user geography accuracy.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "user": { + "geo": { + "accuracy": -1 + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.user.geo.accuracy must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json new file mode 100644 index 00000000000..2534dd626ea --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video height.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "h": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json new file mode 100644 index 00000000000..07c1af1d9a1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video max bitrate.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "maxbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.maxbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json new file mode 100644 index 00000000000..54ead28e8e2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video min bitrate.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "minbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.minbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json new file mode 100644 index 00000000000..cf50e8eddd7 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video width.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "w": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 7735d886730..f46a8c2a1d5 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -17,12 +17,12 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/util/iputil" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" @@ -197,7 +197,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re vo.VideoRequest = videoBidReq - var bidReq = &openrtb.BidRequest{} + var bidReq = &openrtb2.BidRequest{} if deps.defaultRequest { if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil { err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err) @@ -370,13 +370,13 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo vo.Errors = append(vo.Errors, errL...) } -func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) ([]openrtb.Imp, []PodError) { +func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) ([]openrtb2.Imp, []PodError) { videoDur := videoReq.PodConfig.DurationRangeSec minDuration, maxDuration := minMax(videoDur) reqExactDur := videoReq.PodConfig.RequireExactDuration videoData := videoReq.Video - finalImpsArray := make([]openrtb.Imp, 0) + finalImpsArray := make([]openrtb2.Imp, 0) for ind, pod := range videoReq.PodConfig.Pods { //load stored impression @@ -400,7 +400,7 @@ func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVide } impDivNumber := numImps / len(videoDur) - impsArray := make([]openrtb.Imp, numImps) + impsArray := make([]openrtb2.Imp, numImps) for impInd := range impsArray { newImp := createImpressionTemplate(storedImp, videoData) impsArray[impInd] = newImp @@ -432,18 +432,18 @@ func max(a, b int) int { return b } -func createImpressionTemplate(imp openrtb.Imp, video *openrtb.Video) openrtb.Imp { +func createImpressionTemplate(imp openrtb2.Imp, video *openrtb2.Video) openrtb2.Imp { //for every new impression we need to have it's own copy of video object, because we customize it in further processing newVideo := *video imp.Video = &newVideo return imp } -func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb.Imp, []error) { +func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) defer cancel() - impr := openrtb.Imp{} + impr := openrtb2.Imp{} _, imp, err := deps.storedReqFetcher.FetchRequests(ctx, []string{}, []string{storedImpId}) if err != nil { return impr, err @@ -469,7 +469,7 @@ func minMax(array []int) (int, int) { return min, max } -func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError) (*openrtb_ext.BidResponseVideo, error) { +func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) (*openrtb_ext.BidResponseVideo, error) { adPods := make([]*openrtb_ext.AdPod, 0) anyBidsReturned := false @@ -561,7 +561,7 @@ func getVideoStoredRequestId(request []byte) (string, error) { return string(value), nil } -func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb.BidRequest) error { +func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb2.BidRequest) error { if videoRequest.Site != nil { bidRequest.Site = videoRequest.Site diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index a70d45ac3b8..16dc02ee97b 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/analytics" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" @@ -364,7 +364,7 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -375,13 +375,13 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -407,7 +407,7 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { mimes = append(mimes, "") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) req := openrtb_ext.BidRequestVideo{ StoredRequestId: "", @@ -419,7 +419,7 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 0, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -476,7 +476,7 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -487,13 +487,13 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -546,7 +546,7 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -557,16 +557,16 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -604,7 +604,7 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -615,13 +615,13 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Domain: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -655,7 +655,7 @@ func TestVideoEndpointValidationsMissingVideo(t *testing.T) { }, }, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ @@ -670,16 +670,16 @@ func TestVideoEndpointValidationsMissingVideo(t *testing.T) { } func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} - bid3 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} + bid3 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) @@ -708,16 +708,16 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { } func TestVideoBuildVideoResponseMissedCacheForAllBids(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} - bid3 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} + bid3 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1"}}}`) @@ -742,15 +742,15 @@ func TestVideoBuildVideoResponseMissedCacheForAllBids(t *testing.T) { } func TestVideoBuildVideoResponsePodErrors(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0, 2) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) @@ -785,30 +785,30 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { } func TestVideoBuildVideoResponseNoBids(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0, 0) - openRtbBidResp.SeatBid = make([]openrtb.SeatBid, 0) + openRtbBidResp.SeatBid = make([]openrtb2.SeatBid, 0) bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors) assert.NoError(t, err, "Error should be nil") assert.Len(t, bidRespVideo.AdPods, 0, "AdPods length should be 0") } func TestMergeOpenRTBToVideoRequest(t *testing.T) { - var bidReq = &openrtb.BidRequest{} + var bidReq = &openrtb2.BidRequest{} var videoReq = &openrtb_ext.BidRequestVideo{} - videoReq.App = &openrtb.App{ + videoReq.App = &openrtb2.App{ Domain: "test.com", Bundle: "test.bundle", } - videoReq.Site = &openrtb.Site{ + videoReq.Site = &openrtb2.Site{ Page: "site.com/index", } var dnt int8 = 4 var lmt int8 = 5 - videoReq.Device = openrtb.Device{ + videoReq.Device = openrtb2.Device{ DNT: &dnt, Lmt: &lmt, } @@ -816,11 +816,11 @@ func TestMergeOpenRTBToVideoRequest(t *testing.T) { videoReq.BCat = []string{"test1", "test2"} videoReq.BAdv = []string{"test3", "test4"} - videoReq.Regs = &openrtb.Regs{ + videoReq.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"1NYY","existing":"any","consent":"anyConsent"}`), } - videoReq.User = &openrtb.User{ + videoReq.User = &openrtb2.User{ BuyerUID: "test UID", Yob: 1980, Keywords: "test keywords", @@ -1057,27 +1057,27 @@ func TestHandleErrorDebugLog(t *testing.T) { func TestCreateImpressionTemplate(t *testing.T) { - imp := openrtb.Imp{} - imp.Video = &openrtb.Video{} - imp.Video.Protocols = []openrtb.Protocol{1, 2} + imp := openrtb2.Imp{} + imp.Video = &openrtb2.Video{} + imp.Video.Protocols = []openrtb2.Protocol{1, 2} imp.Video.MIMEs = []string{"video/mp4"} imp.Video.H = 200 imp.Video.W = 400 - imp.Video.PlaybackMethod = []openrtb.PlaybackMethod{5, 6} + imp.Video.PlaybackMethod = []openrtb2.PlaybackMethod{5, 6} - video := openrtb.Video{} - video.Protocols = []openrtb.Protocol{3, 4} + video := openrtb2.Video{} + video.Protocols = []openrtb2.Protocol{3, 4} video.MIMEs = []string{"video/flv"} video.H = 300 video.W = 0 - video.PlaybackMethod = []openrtb.PlaybackMethod{7, 8} + video.PlaybackMethod = []openrtb2.PlaybackMethod{7, 8} res := createImpressionTemplate(imp, &video) - assert.Equal(t, res.Video.Protocols, []openrtb.Protocol{3, 4}, "Incorrect video protocols") + assert.Equal(t, res.Video.Protocols, []openrtb2.Protocol{3, 4}, "Incorrect video protocols") assert.Equal(t, res.Video.MIMEs, []string{"video/flv"}, "Incorrect video MIMEs") assert.Equal(t, int(res.Video.H), 300, "Incorrect video height") assert.Equal(t, int(res.Video.W), 0, "Incorrect video width") - assert.Equal(t, res.Video.PlaybackMethod, []openrtb.PlaybackMethod{7, 8}, "Incorrect video playback method") + assert.Equal(t, res.Video.PlaybackMethod, []openrtb2.PlaybackMethod{7, 8}, "Incorrect video playback method") } func TestCCPA(t *testing.T) { @@ -1331,20 +1331,20 @@ func (cf mockVideoStoredReqFetcher) FetchRequests(ctx context.Context, requestID } type mockExchangeVideo struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest if debugLog != nil && debugLog.Enabled { m.cache.called = true } ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video","dealpriority":0,"dealtiersatisfied":false},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ {ID: "01", ImpID: "1_0", Ext: ext}, {ID: "02", ImpID: "1_1", Ext: ext}, {ID: "03", ImpID: "1_2", Ext: ext}, @@ -1367,20 +1367,20 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionR } type mockExchangeAppendBidderNames struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest if debugLog != nil && debugLog.Enabled { m.cache.called = true } ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s_appnexus","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ {ID: "01", ImpID: "1_0", Ext: ext}, {ID: "02", ImpID: "1_1", Ext: ext}, {ID: "03", ImpID: "1_2", Ext: ext}, @@ -1403,14 +1403,14 @@ func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r excha } type mockExchangeVideoNoBids struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{}}, + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{}}, }, nil } diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 7420779f787..73a456f54b1 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/lifestreet" @@ -371,7 +371,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { type fakeAdaptedBidder struct{} -func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return nil, nil } @@ -379,11 +379,11 @@ type fakeBidder struct { name string } -func (fakeBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (fakeBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return nil, nil } -func (fakeBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (fakeBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, nil } diff --git a/exchange/auction.go b/exchange/auction.go index 97f82d3c6eb..b3878709c29 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -11,7 +11,7 @@ import ( "time" uuid "github.com/gofrs/uuid" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" @@ -125,7 +125,7 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int } // isNewWinningBid calculates if the new bid (nbid) will win against the current winning bid (wbid) given preferDeals. -func isNewWinningBid(bid, wbid *openrtb.Bid, preferDeals bool) bool { +func isNewWinningBid(bid, wbid *openrtb2.Bid, preferDeals bool) bool { if preferDeals { if len(wbid.DealID) > 0 && len(bid.DealID) == 0 { return false @@ -147,7 +147,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity a.roundedPrices = roundedPrices } -func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, evTracking *eventTracking, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error { +func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, evTracking *eventTracking, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error { var bids, vast, includeBidderKeys, includeWinners bool = targData.includeCacheBids, targData.includeCacheVast, targData.includeBidderKeys, targData.includeWinners if !((bids || vast) && (includeBidderKeys || includeWinners)) { return nil @@ -155,8 +155,8 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, var errs []error expectNumBids := valOrZero(bids, len(a.roundedPrices)) expectNumVast := valOrZero(vast, len(a.roundedPrices)) - bidIndices := make(map[int]*openrtb.Bid, expectNumBids) - vastIndices := make(map[int]*openrtb.Bid, expectNumVast) + bidIndices := make(map[int]*openrtb2.Bid, expectNumBids) + vastIndices := make(map[int]*openrtb2.Bid, expectNumVast) toCache := make([]prebid_cache_client.Cacheable, 0, expectNumBids+expectNumVast) expByImp := make(map[string]int64) competitiveExclusion := false @@ -257,7 +257,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } if bids { - a.cacheIds = make(map[*openrtb.Bid]string, len(bidIndices)) + a.cacheIds = make(map[*openrtb2.Bid]string, len(bidIndices)) for index, bid := range bidIndices { if ids[index] != "" { a.cacheIds[bid] = ids[index] @@ -265,7 +265,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } if vast { - a.vastCacheIds = make(map[*openrtb.Bid]string, len(vastIndices)) + a.vastCacheIds = make(map[*openrtb2.Bid]string, len(vastIndices)) for index, bid := range vastIndices { if ids[index] != "" { if competitiveExclusion && strings.HasSuffix(ids[index], hbCacheID) { @@ -282,7 +282,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, // makeVAST returns some VAST XML for the given bid. If AdM is defined, // it takes precedence. Otherwise the Nurl will be wrapped in a redirect tag. -func makeVAST(bid *openrtb.Bid) string { +func makeVAST(bid *openrtb2.Bid) string { if bid.AdM == "" { return `` + `prebid.org wrapper` + @@ -355,7 +355,7 @@ type auction struct { // roundedPrices stores the price strings rounded for each bid according to the price granularity. roundedPrices map[*pbsOrtbBid]string // cacheIds stores the UUIDs from Prebid Cache for fetching the full bid JSON. - cacheIds map[*openrtb.Bid]string + cacheIds map[*openrtb2.Bid]string // vastCacheIds stores UUIDS from Prebid cache for fetching the VAST markup to video bids. - vastCacheIds map[*openrtb.Bid]string + vastCacheIds map[*openrtb2.Bid]string } diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 7e8a8a8cec9..bd32607bdf3 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -11,17 +11,17 @@ import ( "strconv" "testing" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/mxmCherry/openrtb" "github.com/stretchr/testify/assert" ) func TestMakeVASTGiven(t *testing.T) { const expect = `` - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ AdM: expect, } vast := makeVAST(bid) @@ -35,7 +35,7 @@ func TestMakeVASTNurl(t *testing.T) { `` + `` + `` - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ NURL: url, } vast := makeVAST(bid) @@ -268,45 +268,45 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { func TestNewAuction(t *testing.T) { bid1p077 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 0.77, }, } bid1p123 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 1.23, }, } bid1p230 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 2.30, }, } bid1p088d := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 0.88, DealID: "SpecialDeal", }, } bid1p166d := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 1.66, DealID: "BigDeal", }, } bid2p123 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp2", Price: 1.23, }, } bid2p144 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp2", Price: 1.44, }, @@ -486,7 +486,7 @@ func TestNewAuction(t *testing.T) { } type cacheSpec struct { - BidRequest openrtb.BidRequest `json:"bidRequest"` + BidRequest openrtb2.BidRequest `json:"bidRequest"` PbsBids []pbsBid `json:"pbsBids"` ExpectedCacheables []prebid_cache_client.Cacheable `json:"expectedCacheables"` DefaultTTLs config.DefaultTTLs `json:"defaultTTLs"` @@ -500,7 +500,7 @@ type cacheSpec struct { } type pbsBid struct { - Bid *openrtb.Bid `json:"bid"` + Bid *openrtb2.Bid `json:"bid"` BidType openrtb_ext.BidType `json:"bidType"` Bidder openrtb_ext.BidderName `json:"bidder"` } diff --git a/exchange/bidder.go b/exchange/bidder.go index b0818dfdbe3..d6b8028c2e9 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,9 +16,9 @@ import ( "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" - "github.com/mxmCherry/openrtb" - nativeRequests "github.com/mxmCherry/openrtb/native/request" - nativeResponse "github.com/mxmCherry/openrtb/native/response" + nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v14/native1/response" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -49,7 +49,7 @@ 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, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -62,7 +62,7 @@ type adaptedBidder interface { // pbsOrtbBid.dealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. // pbsOrtbBid.dealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false type pbsOrtbBid struct { - bid *openrtb.Bid + bid *openrtb2.Bid bidType openrtb_ext.BidType bidTargets map[string]string bidVideo *openrtb_ext.ExtBidPrebidVideo @@ -73,7 +73,7 @@ type pbsOrtbBid struct { // pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder. // -// This is distinct from the openrtb.SeatBid so that the prebid-server ext can be passed back with typesafety. +// This is distinct from the openrtb2.SeatBid so that the prebid-server ext can be passed back with typesafety. type pbsOrtbSeatBid struct { // bids is the list of bids which this adaptedBidder wishes to make. bids []*pbsOrtbBid @@ -124,7 +124,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -248,7 +248,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi return seatBid, errs } -func addNativeTypes(bid *openrtb.Bid, request *openrtb.BidRequest) (*nativeResponse.Response, []error) { +func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { var errs []error var nativeMarkup *nativeResponse.Response if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { @@ -307,7 +307,7 @@ func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Requ return nil } -func getNativeImpByImpID(impID string, request *openrtb.BidRequest) (*openrtb.Native, error) { +func getNativeImpByImpID(impID string, request *openrtb2.BidRequest) (*openrtb2.Native, error) { for _, impInRequest := range request.Imp { if impInRequest.ID == impID && impInRequest.Native != nil { return impInRequest.Native, nil diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index e4eb7b9574e..8cc5b5e9383 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -16,7 +16,9 @@ import ( "time" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" + nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v14/native1/response" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -26,9 +28,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - - nativeRequests "github.com/mxmCherry/openrtb/native/request" - nativeResponse "github.com/mxmCherry/openrtb/native/response" ) // TestSingleBidder makes sure that the following things work if the Bidder needs only one request. @@ -76,14 +75,14 @@ func TestSingleBidder(t *testing.T) { mockBidderResponse := &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: firstInitialPrice, }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, }, { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: secondInitialPrice, }, BidType: openrtb_ext.BidTypeVideo, @@ -96,7 +95,7 @@ func TestSingleBidder(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -158,11 +157,11 @@ func TestMultiBidder(t *testing.T) { mockBidderResponse := &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeVideo, }, }, @@ -185,7 +184,7 @@ func TestMultiBidder(t *testing.T) { } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -515,7 +514,7 @@ func TestMultiCurrencies(t *testing.T) { mockBidderResponses[i] = &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: bid.price, }, BidType: openrtb_ext.BidTypeBanner, @@ -556,7 +555,7 @@ func TestMultiCurrencies(t *testing.T) { seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), @@ -681,7 +680,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { mockBidderResponses[i] = &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, }, @@ -701,7 +700,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), @@ -844,7 +843,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { { Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, }, @@ -872,7 +871,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { ) seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{ + &openrtb2.BidRequest{ Cur: tc.bidRequestCurrencies, }, "test", @@ -984,27 +983,27 @@ func TestMobileNativeTypes(t *testing.T) { reqURL := server.URL testCases := []struct { - mockBidderRequest *openrtb.BidRequest + mockBidderRequest *openrtb2.BidRequest mockBidderResponse *adapters.BidderResponse expectedValue string description string }{ { - mockBidderRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ + mockBidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ { ID: "some-imp-id", - Native: &openrtb.Native{ + Native: &openrtb2.Native{ Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}", }, }, }, - App: &openrtb.App{}, + App: &openrtb2.App{}, }, mockBidderResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ImpID: "some-imp-id", AdM: "{\"assets\":[{\"id\":2,\"img\":{\"url\":\"http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg\",\"w\":989,\"h\":742,\"ext\":{\"appnexus\":{\"prevent_crop\":0}}}},{\"id\":1,\"title\":{\"text\":\"This is a Prebid Native Creative\"}},{\"id\":3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\":4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://some-url.com\"},\"imptrackers\":[\"http://someimptracker.com\"],\"jstracker\":\"some-js-tracker\"}", Price: 10, @@ -1017,21 +1016,21 @@ func TestMobileNativeTypes(t *testing.T) { description: "Checks types in response", }, { - mockBidderRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ + mockBidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ { ID: "some-imp-id", - Native: &openrtb.Native{ + Native: &openrtb2.Native{ Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}", }, }, }, - App: &openrtb.App{}, + App: &openrtb2.App{}, }, mockBidderResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ImpID: "some-imp-id", AdM: "{\"some-diff-markup\":\"creative\"}", Price: 10, @@ -1079,7 +1078,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1262,7 +1261,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -1467,31 +1466,31 @@ func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { } type goodSingleBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequest *adapters.RequestData httpResponse *adapters.ResponseData bidResponse *adapters.BidderResponse } -func (bidder *goodSingleBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *goodSingleBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request return []*adapters.RequestData{bidder.httpRequest}, nil } -func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponse = response return bidder.bidResponse, nil } type goodMultiHTTPCallsBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequest []*adapters.RequestData httpResponses []*adapters.ResponseData bidResponses []*adapters.BidderResponse bidResponseNumber int } -func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request response := make([]*adapters.RequestData, len(bidder.httpRequest)) @@ -1501,7 +1500,7 @@ func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb.BidRequest return response, nil } -func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { br := bidder.bidResponses[bidder.bidResponseNumber] bidder.bidResponseNumber++ bidder.httpResponses = append(bidder.httpResponses, response) @@ -1510,18 +1509,18 @@ func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb.BidReq } type mixedMultiBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequests []*adapters.RequestData httpResponses []*adapters.ResponseData bidResponse *adapters.BidderResponse } -func (bidder *mixedMultiBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *mixedMultiBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request return bidder.httpRequests, []error{errors.New("The requests weren't ideal.")} } -func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponses = append(bidder.httpResponses, response) return bidder.bidResponse, []error{errors.New("The bidResponse weren't ideal.")} } @@ -1531,11 +1530,11 @@ type bidRejector struct { httpResponse *adapters.ResponseData } -func (bidder *bidRejector) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *bidRejector) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return nil, []error{errors.New("Invalid params on BidRequest.")} } -func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *bidRejector) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponse = response return nil, []error{errors.New("Can't make a response.")} } @@ -1545,11 +1544,11 @@ type notifyingBidder struct { notifyRequest adapters.RequestData } -func (bidder *notifyingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *notifyingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return bidder.requests, nil } -func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, nil } diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index cf74dfb1dd5..ad387cf4fcc 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" @@ -28,7 +28,7 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) @@ -37,7 +37,7 @@ func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRe } // validateBids will run some validation checks on the returned bids and excise any invalid bids -func removeInvalidBids(request *openrtb.BidRequest, seatBid *pbsOrtbSeatBid) []error { +func removeInvalidBids(request *openrtb2.BidRequest, seatBid *pbsOrtbSeatBid) []error { // Exit early if there is nothing to do. if seatBid == nil || len(seatBid.bids) == 0 { return nil diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index e7fc0b046dd..97a11a6b743 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" @@ -16,7 +16,7 @@ func TestAllValidBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -24,7 +24,7 @@ func TestAllValidBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", Price: 0.40, @@ -32,7 +32,7 @@ func TestAllValidBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 3) assert.Len(t, errs, 0) } @@ -52,28 +52,28 @@ func TestAllBadBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", Price: 0.45, CrID: "thisCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", CrID: "thatCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "456", Price: 0.44, CrID: "blah", @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 0) assert.Len(t, errs, 5) } @@ -93,7 +93,7 @@ func TestMixedBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -101,14 +101,14 @@ func TestMixedBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", CrID: "thatCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, @@ -116,7 +116,7 @@ func TestMixedBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "456", Price: 0.44, CrID: "blah", @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 2) assert.Len(t, errs, 3) } @@ -210,7 +210,7 @@ func TestCurrencyBids(t *testing.T) { for _, tc := range currencyTestCases { bids := []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -218,7 +218,7 @@ func TestCurrencyBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", Price: 0.44, @@ -242,7 +242,7 @@ func TestCurrencyBids(t *testing.T) { expectedValidBids = 0 } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ Cur: tc.brqCur, } @@ -257,6 +257,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/events_test.go b/exchange/events_test.go index 1d72739d873..6685918ee6e 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -3,7 +3,7 @@ package exchange import ( "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -60,7 +60,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType} assert.Equal(t, tt.want, evData.makeBidExtEvents(bid, openrtb_ext.BidderOpenx)) }) } @@ -124,7 +124,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType} modifiedJSON, err := evData.modifyBidJSON(bid, openrtb_ext.BidderOpenx, tt.jsonBytes) if tt.want != nil { assert.NoError(t, err, "Unexpected error") diff --git a/exchange/exchange.go b/exchange/exchange.go index d8ac60ba46a..e06f87bf677 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,10 +14,10 @@ import ( "strings" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/stored_requests" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -39,7 +39,7 @@ type extCacheInstructions struct { // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines. type Exchange interface { // HoldAuction executes an OpenRTB v2.5 Auction. - HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error) + HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) } // IdFetcher can find the user's ID for a specific Bidder. @@ -102,7 +102,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid // AuctionRequest holds the bid request for the auction // and all other information needed to process that request type AuctionRequest struct { - BidRequest *openrtb.BidRequest + BidRequest *openrtb2.BidRequest Account config.Account UserSyncs IdFetcher RequestType metrics.RequestType @@ -117,13 +117,13 @@ type AuctionRequest struct { // BidderRequest holds the bidder specific request and all other // information needed to process that bidder request. type BidderRequest struct { - BidRequest *openrtb.BidRequest + BidRequest *openrtb2.BidRequest BidderName openrtb_ext.BidderName BidderCoreName openrtb_ext.BidderName BidderLabels metrics.AdapterLabels } -func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error) { +func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) { var err error requestExt, err := extractBidRequestExt(r.BidRequest) if err != nil { @@ -258,9 +258,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb.BidRequest) bool { +func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool { usersyncIfAmbiguous := e.UsersyncIfAmbiguous - var geo *openrtb.Geo = nil + var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { geo = bidRequest.User.Geo @@ -281,7 +281,7 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb.BidRequest) bool return usersyncIfAmbiguous } -func recordImpMetrics(bidRequest *openrtb.BidRequest, metricsEngine metrics.MetricsEngine) { +func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { for _, impInRequest := range bidRequest.Imp { var impLabels metrics.ImpLabels = metrics.ImpLabels{ BannerImps: impInRequest.Banner != nil, @@ -294,7 +294,7 @@ func recordImpMetrics(bidRequest *openrtb.BidRequest, metricsEngine metrics.Metr } // applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded -func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory map[string]string) []error { +func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory map[string]string) []error { errs := []error{} impDealMap := getDealTiers(bidRequest) @@ -315,7 +315,7 @@ func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory } // getDealTiers creates map of impression to bidder deal tier configuration -func getDealTiers(bidRequest *openrtb.BidRequest) map[string]openrtb_ext.DealTierBidderMap { +func getDealTiers(bidRequest *openrtb2.BidRequest) map[string]openrtb_ext.DealTierBidderMap { impDealMap := make(map[string]openrtb_ext.DealTierBidderMap) for _, imp := range bidRequest.Imp { @@ -538,19 +538,19 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb.BidResponse, error) { - bidResponse := new(openrtb.BidResponse) +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb2.BidResponse, error) { + bidResponse := new(openrtb2.BidResponse) var err error bidResponse.ID = bidRequest.ID if len(liveAdapters) == 0 { // signal "Invalid Request" if no valid bidders. - bidResponse.NBR = openrtb.NoBidReasonCode.Ptr(openrtb.NoBidReasonCodeInvalidRequest) + bidResponse.NBR = openrtb2.NoBidReasonCode.Ptr(openrtb2.NoBidReasonCodeInvalidRequest) } // Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid // objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go. - seatBids := make([]openrtb.SeatBid, 0, len(liveAdapters)) + seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters)) for _, a := range liveAdapters { //while processing every single bib, do we need to handle categories here? if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 { @@ -825,8 +825,8 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb // Return an openrtb seatBid for a bidder // BuildBidResponse is responsible for ensuring nil bid seatbids are not included -func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb.SeatBid { - seatBid := &openrtb.SeatBid{ +func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb2.SeatBid { + seatBid := &openrtb2.SeatBid{ Seat: adapter.String(), Group: 0, // Prebid cannot support roadblocking } @@ -840,8 +840,8 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B return seatBid } -func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb.Bid, []error) { - result := make([]openrtb.Bid, 0, len(bids)) +func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb2.Bid, []error) { + result := make([]openrtb2.Bid, 0, len(bids)) errs := make([]error, 0, 1) for _, bid := range bids { @@ -890,7 +890,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid) (json return json.Marshal(extMap) } -// If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, +// If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) { uuid, found := findCacheID(bid, auction) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 3eaa625f74f..1cffa79b127 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -29,7 +30,6 @@ import ( "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -75,7 +75,7 @@ func TestNewExchange(t *testing.T) { } } -// The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb.BidResponse, error) +// The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error) // and check whether the returned request successfully prints any '&' characters as it should // To do so, we: // 1) Write the endpoint adapter URL with an '&' character into a new config,Configuration struct @@ -123,16 +123,16 @@ func TestCharacterEscape(t *testing.T) { adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, 1) adapterBids["appnexus"] = &pbsOrtbSeatBid{currency: "USD"} - //An openrtb.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465 - bidRequest := &openrtb.BidRequest{ + //An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465 + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`), @@ -163,7 +163,7 @@ func TestCharacterEscape(t *testing.T) { } // TestDebugBehaviour asserts the HttpCalls object is included inside the json "debug" field of the bidResponse extension when the -// openrtb.BidRequest "Test" value is set to 1 or the openrtb.BidRequest.Ext.Debug boolean field is set to true +// openrtb2.BidRequest "Test" value is set to 1 or the openrtb2.BidRequest.Ext.Debug boolean field is set to true func TestDebugBehaviour(t *testing.T) { // Define test cases @@ -258,15 +258,15 @@ func TestDebugBehaviour(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", err) } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, } @@ -442,15 +442,15 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { debugLog := DebugLog{Enabled: true} for _, testCase := range testCases { - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, } @@ -637,7 +637,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { bidResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{AdM: sampleAd}, + Bid: &openrtb2.Bid{AdM: sampleAd}, }, }, }, @@ -654,14 +654,14 @@ func TestReturnCreativeEndToEnd(t *testing.T) { e.categoriesFetcher = categoriesFetcher // Define mock incoming bid requeset - mockBidRequest := &openrtb.BidRequest{ + mockBidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, } // Run tests @@ -750,7 +750,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { liveAdapters := []openrtb_ext.BidderName{bidderName} //adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, - bids := []*openrtb.Bid{ + bids := []*openrtb2.Bid{ { ID: "some-imp-id", ImpID: "", @@ -782,7 +782,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, } auc := &auction{ - cacheIds: map[*openrtb.Bid]string{ + cacheIds: map[*openrtb2.Bid]string{ bids[0]: testUUID, }, } @@ -816,14 +816,14 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }, } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", TMax: 1000, - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "test-div", - Secure: openrtb.Int8Ptr(0), - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, + Secure: openrtb2.Int8Ptr(0), + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(` { "rubicon": { "accountId": 1001, @@ -838,9 +838,9 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "http://rubitest.com/index.html", - Publisher: &openrtb.Publisher{ID: "1001"}, + Publisher: &openrtb2.Publisher{ID: "1001"}, }, Test: 1, Ext: json.RawMessage(`{"prebid": { "cache": { "bids": {}, "vastxml": {} }, "targeting": { "pricegranularity": "med", "includewinners": true, "includebidderkeys": false } }}`), @@ -854,11 +854,11 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { /* 5) Assert we have no errors and the bid response we expected*/ assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") - expectedBidResponse := &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + expectedBidResponse := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Seat: string(bidderName), - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ { Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`), }, @@ -887,7 +887,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { func TestBidReturnsCreative(t *testing.T) { sampleAd := "" - sampleOpenrtbBid := &openrtb.Bid{ID: "some-bid-id", AdM: sampleAd} + sampleOpenrtbBid := &openrtb2.Bid{ID: "some-bid-id", AdM: sampleAd} // Define test cases testCases := []struct { @@ -915,7 +915,7 @@ func TestBidReturnsCreative(t *testing.T) { bidTargets: map[string]string{}, }, } - sampleAuction := &auction{cacheIds: map[*openrtb.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}} + sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}} noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(noBidHandler)) @@ -954,7 +954,7 @@ func TestBidReturnsCreative(t *testing.T) { } func TestGetBidCacheInfo(t *testing.T) { - bid := &openrtb.Bid{ID: "42"} + bid := &openrtb2.Bid{ID: "42"} testCases := []struct { description string scheme string @@ -972,7 +972,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "https://prebid.org/cache?uuid=anyID", @@ -983,7 +983,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{vastCacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{vastCacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "https://prebid.org/cache?uuid=anyID", @@ -1004,7 +1004,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "prebid.org/cache?uuid=anyID", @@ -1012,7 +1012,7 @@ func TestGetBidCacheInfo(t *testing.T) { { description: "Host And Path Not Provided - Without Scheme", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "", @@ -1021,7 +1021,7 @@ func TestGetBidCacheInfo(t *testing.T) { description: "Host And Path Not Provided - With Scheme", scheme: "https", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "", @@ -1032,7 +1032,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: nil, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: false, expectedCacheID: "", expectedCacheURL: "", @@ -1043,7 +1043,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: nil}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: false, expectedCacheID: "", expectedCacheURL: "", @@ -1103,15 +1103,15 @@ func TestBidResponseCurrency(t *testing.T) { liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`), @@ -1123,7 +1123,7 @@ func TestBidResponseCurrency(t *testing.T) { var errList []error - sampleBid := &openrtb.Bid{ + sampleBid := &openrtb2.Bid{ ID: "some-imp-id", Price: 9.517803, W: 300, @@ -1131,10 +1131,10 @@ func TestBidResponseCurrency(t *testing.T) { Ext: nil, } aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeBanner}} - sampleSeatBid := []openrtb.SeatBid{ + sampleSeatBid := []openrtb2.SeatBid{ { Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ { ID: "some-imp-id", Price: 9.517803, @@ -1145,13 +1145,13 @@ func TestBidResponseCurrency(t *testing.T) { }, }, } - emptySeatBid := []openrtb.SeatBid{} + emptySeatBid := []openrtb2.SeatBid{} // Test cases type aTest struct { description string adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid - expectedBidResponse *openrtb.BidResponse + expectedBidResponse *openrtb2.BidResponse } testCases := []aTest{ { @@ -1162,7 +1162,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "USD", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: sampleSeatBid, Cur: "USD", @@ -1178,7 +1178,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "USD", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: emptySeatBid, Cur: "", @@ -1194,7 +1194,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: sampleSeatBid, Cur: "", @@ -1210,7 +1210,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: emptySeatBid, Cur: "", @@ -1305,39 +1305,39 @@ func newCategoryFetcher(directory string) (stored_requests.CategoryFetcher, erro // newRaceCheckingRequest builds a BidRequest from all the params in the // adapters/{bidder}/{bidder}test/params/race/*.json files -func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest { +func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) - return &openrtb.BidRequest{ - Site: &openrtb.Site{ + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", IFA: "ifa", IP: "132.173.230.74", DNT: &dnt, Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ COPPA: 1, Ext: json.RawMessage(`{"gdpr":1}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1347,7 +1347,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest { }, Ext: buildImpExt(t, "banner"), }, { - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, MinDuration: 1, MaxDuration: 300, @@ -1399,14 +1399,14 @@ func TestPanicRecovery(t *testing.T) { BidderName: "bidder1", BidderCoreName: "appnexus", BidderLabels: apnLabels, - BidRequest: &openrtb.BidRequest{ + BidRequest: &openrtb2.BidRequest{ ID: "b-1", }, }, { BidderName: "bidder2", BidderCoreName: "bidder2", - BidRequest: &openrtb.BidRequest{ + BidRequest: &openrtb2.BidRequest{ ID: "b-2", }, }, @@ -1479,23 +1479,23 @@ func TestPanicRecoveryHighLevel(t *testing.T) { e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} - request := &openrtb.BidRequest{ - Site: &openrtb.Site{ + request := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1685,7 +1685,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } } -func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) []string { +func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string { if splitImps, err := splitImps(req.Imp); err != nil { t.Errorf("%s: Failed to parse Bidders from request: %v", context, err) return nil @@ -1701,7 +1701,7 @@ func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) // extractResponseTimes validates the format of bid.ext.responsetimemillis, and then removes it. // This is done because the response time will change from run to run, so it's impossible to hardcode a value // into the JSON. The best we can do is make sure that the property exists. -func extractResponseTimes(t *testing.T, context string, bid *openrtb.BidResponse) map[string]int { +func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidResponse) map[string]int { if data, dataType, _, err := jsonparser.Get(bid.Ext, "responsetimemillis"); err != nil || dataType != jsonparser.Object { t.Errorf("%s: Exchange did not return ext.responsetimemillis object: %v", context, err) return nil @@ -1852,10 +1852,10 @@ func TestCategoryMapping(t *testing.T) { cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} @@ -1907,10 +1907,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} @@ -1962,9 +1962,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} @@ -2044,9 +2044,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} @@ -2094,11 +2094,11 @@ func TestCategoryDedupe(t *testing.T) { cats2 := []string{"IAB1-4"} // bid3 will be same price, category, and duration as bid1 so one of them should get removed cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} + bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false} @@ -2173,11 +2173,11 @@ func TestNoCategoryDedupe(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} + bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} @@ -2256,8 +2256,8 @@ func TestCategoryMappingBidderName(t *testing.T) { cats1 := []string{"IAB1-1"} cats2 := []string{"IAB1-2"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} @@ -2310,8 +2310,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { cats1 := []string{"IAB1-1"} cats2 := []string{"IAB1-2"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} @@ -2368,7 +2368,7 @@ func TestBidRejectionErrors(t *testing.T) { testCases := []struct { description string reqExt openrtb_ext.ExtRequest - bids []*openrtb.Bid + bids []*openrtb2.Bid duration int expectedRejections []string expectedCatDur string @@ -2376,7 +2376,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to not containing a category", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{}, W: 1, H: 1}, }, duration: 30, @@ -2387,7 +2387,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to missing category mapping file", reqExt: invalidReqExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, duration: 30, @@ -2398,7 +2398,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to duration exceeding maximum", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, duration: 70, @@ -2409,7 +2409,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to duplicate bid", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, @@ -2471,8 +2471,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} - bidApn1 := openrtb.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bidApn2 := openrtb.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} + bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} @@ -2592,9 +2592,9 @@ func TestApplyDealSupport(t *testing.T) { bidderName := openrtb_ext.BidderName("appnexus") for _, test := range testCases { - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "imp_id1", Ext: test.impExt, @@ -2602,7 +2602,7 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2628,20 +2628,20 @@ func TestApplyDealSupport(t *testing.T) { func TestGetDealTiers(t *testing.T) { testCases := []struct { description string - request openrtb.BidRequest + request openrtb2.BidRequest expected map[string]openrtb_ext.DealTierBidderMap }{ { description: "None", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{}, + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, }, expected: map[string]openrtb_ext.DealTierBidderMap{}, }, { description: "One", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}`)}, }, }, @@ -2651,8 +2651,8 @@ func TestGetDealTiers(t *testing.T) { }, { description: "Many", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}`)}, }, @@ -2664,8 +2664,8 @@ func TestGetDealTiers(t *testing.T) { }, { description: "Many - Skips Malformed", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": "wrong type"}}`)}, }, @@ -2769,7 +2769,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2795,14 +2795,14 @@ type exchangeSpec struct { } type exchangeRequest struct { - OrtbRequest openrtb.BidRequest `json:"ortbRequest"` - Usersyncs map[string]string `json:"usersyncs"` + OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` + Usersyncs map[string]string `json:"usersyncs"` } type exchangeResponse struct { - Bids *openrtb.BidResponse `json:"bids"` - Error string `json:"error,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` + Bids *openrtb2.BidResponse `json:"bids"` + Error string `json:"error,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } type bidderSpec struct { @@ -2812,8 +2812,8 @@ type bidderSpec struct { } type bidderRequest struct { - OrtbRequest openrtb.BidRequest `json:"ortbRequest"` - BidAdjustment float64 `json:"bidAdjustment"` + OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` + BidAdjustment float64 `json:"bidAdjustment"` } type bidderResponse struct { @@ -2832,8 +2832,8 @@ type bidderSeatBid struct { // bidderBid is basically a subset of pbsOrtbBid from exchange/bidder.go. // See the comment on bidderSeatBid for more info. type bidderBid struct { - Bid *openrtb.Bid `json:"ortbBid,omitempty"` - Type string `json:"bidType,omitempty"` + Bid *openrtb2.Bid `json:"ortbBid,omitempty"` + Type string `json:"bidType,omitempty"` } type mockIdFetcher map[string]string @@ -2857,7 +2857,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -2900,7 +2900,7 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidR return } -func diffOrtbRequests(t *testing.T, description string, expected *openrtb.BidRequest, actual *openrtb.BidRequest) { +func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() actualJSON, err := json.Marshal(actual) if err != nil { @@ -2915,7 +2915,7 @@ func diffOrtbRequests(t *testing.T, description string, expected *openrtb.BidReq diffJson(t, description, actualJSON, expectedJSON) } -func diffOrtbResponses(t *testing.T, description string, expected *openrtb.BidResponse, actual *openrtb.BidResponse) { +func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidResponse, actual *openrtb2.BidResponse) { t.Helper() // The OpenRTB spec is wonky here. Since "bidresponse.seatbid" is an array, order technically matters to any JSON diff or // deep equals method. However, for all intents and purposes it really *doesn't* matter. ...so this nasty logic makes compares @@ -2938,8 +2938,8 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb.BidRe diffJson(t, description, actualJSON, expectedJSON) } -func mapifySeatBids(t *testing.T, context string, seatBids []openrtb.SeatBid) map[string]*openrtb.SeatBid { - seatMap := make(map[string]*openrtb.SeatBid, len(seatBids)) +func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) map[string]*openrtb2.SeatBid { + seatMap := make(map[string]*openrtb2.SeatBid, len(seatBids)) for i := 0; i < len(seatBids); i++ { seatName := seatBids[i].Seat if _, ok := seatMap[seatName]; ok { @@ -3036,7 +3036,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/exchange/gdpr.go b/exchange/gdpr.go index e861f948773..a6b4a22dc8f 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,12 +3,12 @@ package exchange import ( "encoding/json" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/gdpr" ) // ExtractGDPR will pull the gdpr flag from an openrtb request -func extractGDPR(bidRequest *openrtb.BidRequest) (gdpr.Signal, error) { +func extractGDPR(bidRequest *openrtb2.BidRequest) (gdpr.Signal, error) { var re regsExt var err error if bidRequest.Regs != nil && bidRequest.Regs.Ext != nil { @@ -21,7 +21,7 @@ func extractGDPR(bidRequest *openrtb.BidRequest) (gdpr.Signal, error) { } // ExtractConsent will pull the consent string from an openrtb request -func extractConsent(bidRequest *openrtb.BidRequest) (consent string, err error) { +func extractConsent(bidRequest *openrtb2.BidRequest) (consent string, err error) { var ue userExt if bidRequest.User != nil && bidRequest.User.Ext != nil { err = json.Unmarshal(bidRequest.User.Ext, &ue) diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index 351939091da..420b299f2cc 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/gdpr" "github.com/stretchr/testify/assert" ) @@ -12,23 +12,23 @@ import ( func TestExtractGDPR(t *testing.T) { tests := []struct { description string - giveRegs *openrtb.Regs + giveRegs *openrtb2.Regs wantGDPR gdpr.Signal wantError bool }{ { description: "Regs Ext GDPR = 0", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 1", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = null", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, wantGDPR: gdpr.SignalAmbiguous, }, { @@ -38,19 +38,19 @@ func TestExtractGDPR(t *testing.T) { }, { description: "Regs Ext is nil", - giveRegs: &openrtb.Regs{Ext: nil}, + giveRegs: &openrtb2.Regs{Ext: nil}, wantGDPR: gdpr.SignalAmbiguous, }, { description: "JSON unmarshal error", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"`)}, wantGDPR: gdpr.SignalAmbiguous, wantError: true, }, } for _, tt := range tests { - bidReq := openrtb.BidRequest{ + bidReq := openrtb2.BidRequest{ Regs: tt.giveRegs, } @@ -68,23 +68,23 @@ func TestExtractGDPR(t *testing.T) { func TestExtractConsent(t *testing.T) { tests := []struct { description string - giveUser *openrtb.User + giveUser *openrtb2.User wantConsent string wantError bool }{ { description: "User Ext Consent is not empty", - giveUser: &openrtb.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { description: "User Ext Consent is empty", - giveUser: &openrtb.User{Ext: json.RawMessage(`{"consent": ""}`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": ""}`)}, wantConsent: "", }, { description: "User Ext is nil", - giveUser: &openrtb.User{Ext: nil}, + giveUser: &openrtb2.User{Ext: nil}, wantConsent: "", }, { @@ -94,14 +94,14 @@ func TestExtractConsent(t *testing.T) { }, { description: "JSON unmarshal error", - giveUser: &openrtb.User{Ext: json.RawMessage(`{`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{`)}, wantConsent: "", wantError: true, }, } for _, tt := range tests { - bidReq := openrtb.BidRequest{ + bidReq := openrtb2.BidRequest{ User: tt.giveUser, } diff --git a/exchange/legacy.go b/exchange/legacy.go index b4845b76c69..ea8b65c28f0 100644 --- a/exchange/legacy.go +++ b/exchange/legacy.go @@ -6,7 +6,7 @@ import ( "errors" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" @@ -33,7 +33,7 @@ type adaptedAdapter struct { // // This is not ideal. OpenRTB provides a superset of the legacy data structures. // For requests which use those features, the best we can do is respond with "no bid". -func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name) if legacyRequest == nil || legacyBidder == nil { return nil, errs @@ -58,7 +58,7 @@ func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.B // toLegacyAdapterInputs is a best-effort transformation of an OpenRTB BidRequest into the args needed to run a legacy Adapter. // If the OpenRTB request is too complex, it fails with an error. // If the error is nil, then the PBSRequest and PBSBidder are valid. -func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { +func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { legacyReq, err := bidder.toLegacyRequest(req) if err != nil { return nil, nil, []error{err} @@ -72,7 +72,7 @@ func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb.BidRequest, nam return legacyReq, legacyBidder, errs } -func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBSRequest, error) { +func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb2.BidRequest) (*pbs.PBSRequest, error) { acctId, err := toAccountId(req) if err != nil { return nil, err @@ -126,7 +126,7 @@ func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBS App: req.App, Device: req.Device, // PBSUser is excluded because rubicon is the only adapter which reads from it, and they're supporting OpenRTB directly - // SDK is excluded because that information doesn't exist in OpenRTB. + // SDK is excluded because that information doesn't exist in openrtb2. // Bidders is excluded because no legacy adapters read from it User: req.User, Cookie: cookie, @@ -137,7 +137,7 @@ func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBS }, nil } -func toAccountId(req *openrtb.BidRequest) (string, error) { +func toAccountId(req *openrtb2.BidRequest) (string, error) { if req.Site != nil && req.Site.Publisher != nil { return req.Site.Publisher.ID, nil } @@ -147,14 +147,14 @@ func toAccountId(req *openrtb.BidRequest) (string, error) { return "", errors.New("bidrequest.site.publisher.id or bidrequest.app.publisher.id required for legacy bidders.") } -func toTransactionId(req *openrtb.BidRequest) (string, error) { +func toTransactionId(req *openrtb2.BidRequest) (string, error) { if req.Source != nil { return req.Source.TID, nil } return "", errors.New("bidrequest.source.tid required for legacy bidders.") } -func toSecure(req *openrtb.BidRequest) (secure int8, err error) { +func toSecure(req *openrtb2.BidRequest) (secure int8, err error) { secure = -1 for _, imp := range req.Imp { if imp.Secure != nil { @@ -181,7 +181,7 @@ func toSecure(req *openrtb.BidRequest) (secure int8, err error) { return } -func toLegacyBidder(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { +func toLegacyBidder(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { adUnits, errs := toPBSAdUnits(req) if len(adUnits) > 0 { return &pbs.PBSBidder{ @@ -202,7 +202,7 @@ func toLegacyBidder(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs. } } -func toPBSAdUnits(req *openrtb.BidRequest) ([]pbs.PBSAdUnit, []error) { +func toPBSAdUnits(req *openrtb2.BidRequest) ([]pbs.PBSAdUnit, []error) { adUnits := make([]pbs.PBSAdUnit, len(req.Imp)) var errs []error = nil nextAdUnit := 0 @@ -217,8 +217,8 @@ func toPBSAdUnits(req *openrtb.BidRequest) ([]pbs.PBSAdUnit, []error) { return adUnits[:nextAdUnit], errs } -func initPBSAdUnit(imp *openrtb.Imp, adUnit *pbs.PBSAdUnit) error { - var sizes []openrtb.Format = nil +func initPBSAdUnit(imp *openrtb2.Imp, adUnit *pbs.PBSAdUnit) error { + var sizes []openrtb2.Format = nil video := pbs.PBSVideo{} if imp.Video != nil { @@ -242,7 +242,7 @@ func initPBSAdUnit(imp *openrtb.Imp, adUnit *pbs.PBSAdUnit) error { } // Fixes #360 if imp.Video.W != 0 && imp.Video.H != 0 { - sizes = append(sizes, openrtb.Format{ + sizes = append(sizes, openrtb2.Format{ W: imp.Video.W, H: imp.Video.H, }) @@ -324,12 +324,12 @@ func transformBid(legacyBid *pbs.PBSBid) (*pbsOrtbBid, error) { }, nil } -func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb.Bid { - return &openrtb.Bid{ +func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb2.Bid { + return &openrtb2.Bid{ ID: legacyBid.BidID, ImpID: legacyBid.AdUnitCode, CrID: legacyBid.Creative_id, - // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb.Bid + // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb2.Bid // legacyBid.BidderCode is handled by the exchange, which already knows which bidder we are. // legacyBid.BidHash is ignored, because it doesn't get sent in the response anyway Price: legacyBid.Price, diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go index 2cef4feae40..aa684287997 100644 --- a/exchange/legacy_test.go +++ b/exchange/legacy_test.go @@ -11,7 +11,7 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" @@ -20,35 +20,35 @@ import ( ) func TestSiteVideo(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", TMax: 1000, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "http://www.site.com", Domain: "site.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "host-id", BuyerUID: "bidder-id", }, Test: 1, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, MinDuration: 20, MaxDuration: 40, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, - StartDelay: openrtb.StartDelayGenericMidRoll.Ptr(), + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, + StartDelay: openrtb2.StartDelayGenericMidRoll.Ptr(), }, - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -85,7 +85,7 @@ func TestSiteVideo(t *testing.T) { func TestAppBanner(t *testing.T) { ortbRequest := newAppOrtbRequest() ortbRequest.TMax = 1000 - ortbRequest.User = &openrtb.User{ + ortbRequest.User = &openrtb2.User{ ID: "host-id", BuyerUID: "bidder-id", } @@ -187,8 +187,8 @@ func TestBidTransforms(t *testing.T) { func TestInsecureImps(t *testing.T) { insecure := int8(0) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &insecure, }, { Secure: &insecure, @@ -205,8 +205,8 @@ func TestInsecureImps(t *testing.T) { func TestSecureImps(t *testing.T) { secure := int8(1) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &secure, }, { Secure: &secure, @@ -224,8 +224,8 @@ func TestSecureImps(t *testing.T) { func TestMixedSecureImps(t *testing.T) { insecure := int8(0) secure := int8(1) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &insecure, }, { Secure: &secure, @@ -237,21 +237,21 @@ func TestMixedSecureImps(t *testing.T) { } } -func newAppOrtbRequest() *openrtb.BidRequest { - return &openrtb.BidRequest{ +func newAppOrtbRequest() *openrtb2.BidRequest { + return &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -262,20 +262,20 @@ func newAppOrtbRequest() *openrtb.BidRequest { } func TestErrorResponse(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -300,20 +300,20 @@ func TestErrorResponse(t *testing.T) { } func TestWithTargeting(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -343,7 +343,7 @@ func TestWithTargeting(t *testing.T) { // assertEquivalentFields compares the OpenRTB request with the legacy request, using the mappings defined here: // https://gist.github.com/dbemiller/68aa3387189fa17d3addfb9818dd4d97 -func assertEquivalentRequests(t *testing.T, req *openrtb.BidRequest, legacy *pbs.PBSRequest) { +func assertEquivalentRequests(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSRequest) { if req.Site != nil { if req.Site.Publisher.ID != legacy.AccountID { t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID) @@ -399,7 +399,7 @@ func assertEquivalentRequests(t *testing.T, req *openrtb.BidRequest, legacy *pbs } } -func assertEquivalentBidder(t *testing.T, req *openrtb.BidRequest, legacy *pbs.PBSBidder) { +func assertEquivalentBidder(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSBidder) { if len(req.Imp) != len(legacy.AdUnits) { t.Errorf("Wrong number of Imps. Expected %d, got %d", len(req.Imp), len(legacy.AdUnits)) return @@ -409,7 +409,7 @@ func assertEquivalentBidder(t *testing.T, req *openrtb.BidRequest, legacy *pbs.P } } -func assertEquivalentImp(t *testing.T, index int, imp *openrtb.Imp, legacy *pbs.PBSAdUnit) { +func assertEquivalentImp(t *testing.T, index int, imp *openrtb2.Imp, legacy *pbs.PBSAdUnit) { if imp.ID != legacy.BidID { t.Errorf("imp[%d].id did not translate. OpenRTB %s, legacy %s", index, imp.ID, legacy.BidID) } diff --git a/exchange/targeting.go b/exchange/targeting.go index 87bec6f3077..f79e05be930 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -3,7 +3,7 @@ package exchange import ( "strconv" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -91,9 +91,9 @@ func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.Targ } } -func makeHbSize(bid *openrtb.Bid) string { +func makeHbSize(bid *openrtb2.Bid) string { if bid.W != 0 && bid.H != 0 { - return strconv.FormatUint(bid.W, 10) + "x" + strconv.FormatUint(bid.H, 10) + return strconv.FormatInt(bid.W, 10) + "x" + strconv.FormatInt(bid.H, 10) } return "" } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 2a77c4f7517..2cdf403439f 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -16,14 +17,13 @@ import ( metricsConf "github.com/prebid/prebid-server/metrics/config" metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) // Using this set of bids in more than one test -var mockBids = map[openrtb_ext.BidderName][]*openrtb.Bid{ +var mockBids = map[openrtb_ext.BidderName][]*openrtb2.Bid{ openrtb_ext.BidderAppnexus: {{ ID: "losing-bid", ImpID: "some-imp", @@ -67,7 +67,7 @@ func TestTargetingCache(t *testing.T) { } -func assertKeyExists(t *testing.T, bid *openrtb.Bid, key string, expected bool) { +func assertKeyExists(t *testing.T, bid *openrtb2.Bid, key string, expected bool) { t.Helper() targets := parseTargets(t, bid) if _, ok := targets[key]; ok != expected { @@ -77,7 +77,7 @@ func assertKeyExists(t *testing.T, bid *openrtb.Bid, key string, expected bool) // runAuction takes a bunch of mock bids by Bidder and runs an auction. It returns a map of Bids indexed by their ImpID. // If includeCache is true, the auction will be run with cacheing as well, so the cache targeting keys should exist. -func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid, includeCache bool, includeWinners bool, includeBidderKeys bool, isApp bool) map[string]*openrtb.Bid { +func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid, includeCache bool, includeWinners bool, includeBidderKeys bool, isApp bool) map[string]*openrtb2.Bid { server := httptest.NewServer(http.HandlerFunc(mockServer)) defer server.Close() @@ -99,14 +99,14 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op imps := buildImps(t, mockBids) - req := &openrtb.BidRequest{ + req := &openrtb2.BidRequest{ Imp: imps, Ext: buildTargetingExt(includeCache, includeWinners, includeBidderKeys), } if isApp { - req.App = &openrtb.App{} + req.App = &openrtb2.App{} } else { - req.Site = &openrtb.Site{} + req.Site = &openrtb2.Site{} } auctionRequest := AuctionRequest{ @@ -128,7 +128,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } -func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb_ext.BidderName { +func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { bidders := make([]openrtb_ext.BidderName, 0, len(bids)) for name := range bids { bidders = append(bidders, name) @@ -136,7 +136,7 @@ func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb_e return bidders } -func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { +func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ @@ -166,7 +166,7 @@ func buildTargetingExt(includeCache bool, includeWinners bool, includeBidderKeys return json.RawMessage(`{"prebid":{"targeting":` + targeting + `}}`) } -func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) json.RawMessage { +func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) json.RawMessage { params := make(map[string]json.RawMessage) for bidder := range mockBids { params[string(bidder)] = json.RawMessage(`{"whatever":true}`) @@ -178,7 +178,7 @@ func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bi return ext } -func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb.Imp { +func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb2.Imp { impExt := buildParams(t, mockBids) var s struct{} @@ -189,9 +189,9 @@ func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) } } - imps := make([]openrtb.Imp, 0, len(impIds)) + imps := make([]openrtb2.Imp, 0, len(impIds)) for impId := range impIds { - imps = append(imps, openrtb.Imp{ + imps = append(imps, openrtb2.Imp{ ID: impId, Ext: impExt, }) @@ -199,8 +199,8 @@ func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) return imps } -func buildBidMap(seatBids []openrtb.SeatBid, numBids int) map[string]*openrtb.Bid { - bids := make(map[string]*openrtb.Bid, numBids) +func buildBidMap(seatBids []openrtb2.SeatBid, numBids int) map[string]*openrtb2.Bid { + bids := make(map[string]*openrtb2.Bid, numBids) for _, seatBid := range seatBids { for i := 0; i < len(seatBid.Bid); i++ { bid := seatBid.Bid[i] @@ -210,7 +210,7 @@ func buildBidMap(seatBids []openrtb.SeatBid, numBids int) map[string]*openrtb.Bi return bids } -func parseTargets(t *testing.T, bid *openrtb.Bid) map[string]string { +func parseTargets(t *testing.T, bid *openrtb2.Bid) map[string]string { t.Helper() var parsed openrtb_ext.ExtBid if err := json.Unmarshal(bid.Ext, &parsed); err != nil { @@ -221,10 +221,10 @@ func parseTargets(t *testing.T, bid *openrtb.Bid) map[string]string { type mockTargetingBidder struct { mockServerURL string - bids []*openrtb.Bid + bids []*openrtb2.Bid } -func (m *mockTargetingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (m *mockTargetingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return []*adapters.RequestData{{ Method: "POST", Uri: m.mockServerURL, @@ -233,7 +233,7 @@ func (m *mockTargetingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo }}, nil } -func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidResponse := &adapters.BidderResponse{ Bids: make([]*adapters.TypedBid, len(m.bids)), } @@ -259,15 +259,15 @@ type TargetingTestData struct { ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string } -var bid123 *openrtb.Bid = &openrtb.Bid{ +var bid123 *openrtb2.Bid = &openrtb2.Bid{ Price: 1.23, } -var bid111 *openrtb.Bid = &openrtb.Bid{ +var bid111 *openrtb2.Bid = &openrtb2.Bid{ Price: 1.11, DealID: "mydeal", } -var bid084 *openrtb.Bid = &openrtb.Bid{ +var bid084 *openrtb2.Bid = &openrtb2.Bid{ Price: 0.84, } @@ -396,7 +396,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ }, }, }, - cacheIds: map[*openrtb.Bid]string{ + cacheIds: map[*openrtb2.Bid]string{ bid123: "55555", bid111: "cacheme", }, diff --git a/exchange/utils.go b/exchange/utils.go index 0d9d0e3ced8..f011669e1f2 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,10 +6,10 @@ import ( "fmt" "math/rand" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" @@ -153,7 +153,7 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT return privacyConfig.CCPA.Enforce } -func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { +func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { ccpaPolicy, err := ccpa.ReadFromRequest(orig) if err != nil { return privacy.NilPolicyEnforcer{}, err @@ -172,7 +172,7 @@ func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account return ccpaEnforcer, nil } -func extractLMT(orig *openrtb.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { +func extractLMT(orig *openrtb2.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { return privacy.EnabledPolicyEnforcer{ Enabled: privacyConfig.LMT.Enforce, PolicyEnforcer: lmt.ReadFromRequest(orig), @@ -181,7 +181,7 @@ func extractLMT(orig *openrtb.BidRequest, privacyConfig config.Privacy) privacy. func getAuctionBidderRequests(req AuctionRequest, requestExt *openrtb_ext.ExtRequest, - impsByBidder map[string][]openrtb.Imp, + impsByBidder map[string][]openrtb2.Imp, aliases map[string]string) ([]BidderRequest, []error) { bidderRequests := make([]BidderRequest, 0, len(impsByBidder)) @@ -243,7 +243,7 @@ func getAuctionBidderRequests(req AuctionRequest, return bidderRequests, errs } -func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { +func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { if len(req.Ext) == 0 || unpackedExt == nil { return json.RawMessage(``), nil } @@ -253,7 +253,7 @@ func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (j return json.Marshal(extCopy) } -func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { +func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { const sChainWildCard = "*" var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain @@ -273,7 +273,7 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s // set source if req.Source == nil { - req.Source = &openrtb.Source{} + req.Source = &openrtb2.Source{} } schain := openrtb_ext.ExtRequestPrebidSChain{ SChain: *selectedSChain, @@ -286,7 +286,7 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. // This prevents a Bidder from using these values to figure out who else is involved in the Auction. -func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { +func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { if user == nil { return nil, nil } @@ -328,8 +328,8 @@ func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key. // // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them. -func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, error) { - bidderImps := make(map[string][]openrtb.Imp) +func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { + bidderImps := make(map[string][]openrtb2.Imp) for i, imp := range imps { var impExt map[string]json.RawMessage @@ -433,7 +433,7 @@ func isSpecialField(bidder string) bool { // // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. -func prepareUser(req *openrtb.BidRequest, givenBidder string, coreBidder openrtb_ext.BidderName, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { +func prepareUser(req *openrtb2.BidRequest, givenBidder string, coreBidder openrtb_ext.BidderName, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie := usersyncs.GetId(coreBidder) if id, ok := explicitBuyerUIDs[givenBidder]; ok { @@ -447,9 +447,9 @@ func prepareUser(req *openrtb.BidRequest, givenBidder string, coreBidder openrtb // copyWithBuyerUID either overwrites the BuyerUID property on user with the argument, or returns // a new (empty) User with the BuyerUID already set. -func copyWithBuyerUID(user *openrtb.User, buyerUID string) *openrtb.User { +func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User { if user == nil { - return &openrtb.User{ + return &openrtb2.User{ BuyerUID: buyerUID, } } @@ -462,7 +462,7 @@ func copyWithBuyerUID(user *openrtb.User, buyerUID string) *openrtb.User { } // removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder -func removeUnpermissionedEids(request *openrtb.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { +func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { // ensure request might have eids (as much as we can check before unmarshalling) if request.User == nil || len(request.User.Ext) == 0 { return nil @@ -549,7 +549,7 @@ func removeUnpermissionedEids(request *openrtb.BidRequest, bidder string, reques return nil } -func setUserExtWithCopy(request *openrtb.BidRequest, userExtJSON json.RawMessage) { +func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) { userCopy := *request.User userCopy.Ext = userExtJSON request.User = &userCopy @@ -564,7 +564,7 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN } // parseAliases parses the aliases from the BidRequest -func parseAliases(orig *openrtb.BidRequest) (map[string]string, []error) { +func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { var aliases map[string]string if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliases"); dataType == jsonparser.Object && err == nil { if err := json.Unmarshal(value, &aliases); err != nil { @@ -597,7 +597,7 @@ func randomizeList(list []openrtb_ext.BidderName) { } } -func extractBidRequestExt(bidRequest *openrtb.BidRequest) (*openrtb_ext.ExtRequest, error) { +func extractBidRequestExt(bidRequest *openrtb2.BidRequest) (*openrtb_ext.ExtRequest, error) { requestExt := &openrtb_ext.ExtRequest{} if bidRequest == nil { @@ -660,7 +660,7 @@ func getExtTargetData(requestExt *openrtb_ext.ExtRequest, cacheInstructions *ext return targData } -func getDebugInfo(bidRequest *openrtb.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { +func getDebugInfo(bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { return (bidRequest != nil && bidRequest.Test == 1) || (requestExt != nil && requestExt.Prebid.Debug) } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index f8603f18f4f..05e22548639 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/gdpr" @@ -58,28 +58,28 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest, func TestSplitImps(t *testing.T) { testCases := []struct { description string - givenImps []openrtb.Imp - expectedImps map[string][]openrtb.Imp + givenImps []openrtb2.Imp + expectedImps map[string][]openrtb2.Imp expectedError string }{ { description: "Nil", givenImps: nil, - expectedImps: map[string][]openrtb.Imp{}, + expectedImps: map[string][]openrtb2.Imp{}, expectedError: "", }, { description: "Empty", - givenImps: []openrtb.Imp{}, - expectedImps: map[string][]openrtb.Imp{}, + givenImps: []openrtb2.Imp{}, + expectedImps: map[string][]openrtb2.Imp{}, expectedError: "", }, { description: "1 Imp, 1 Bidder", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, }, @@ -88,10 +88,10 @@ func TestSplitImps(t *testing.T) { }, { description: "1 Imp, 2 Bidders", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"},"bidderB":{"imp1ParamB":"imp1ValueB"}}}}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, }, @@ -103,11 +103,11 @@ func TestSplitImps(t *testing.T) { }, { description: "2 Imps, 1 Bidders Each", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2ParamA":"imp2ValueA"}}}}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2ParamA":"imp2ValueA"}}`)}, @@ -117,11 +117,11 @@ func TestSplitImps(t *testing.T) { }, { description: "2 Imps, 2 Bidders Each", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}}}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, @@ -136,11 +136,11 @@ func TestSplitImps(t *testing.T) { { // This is a "happy path" integration test. Functionality is covered in detail by TestCreateSanitizedImpExt. description: "Other Fields - 2 Imps, 2 Bidders Each", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}},"skadn":"imp1SkAdN"}`)}, {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}},"skadn":"imp2SkAdN"}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"},"skadn":"imp1SkAdN"}`)}, {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"},"skadn":"imp2SkAdN"}`)}, @@ -155,11 +155,11 @@ func TestSplitImps(t *testing.T) { { // This is a "happy path" integration test. Functionality is covered in detail by TestExtractBidderExts. description: "Legacy imp.ext.BIDDER - 2 Imps, 2 Bidders Each", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}`)}, }, - expectedImps: map[string][]openrtb.Imp{ + expectedImps: map[string][]openrtb2.Imp{ "bidderA": { {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, @@ -173,21 +173,21 @@ func TestSplitImps(t *testing.T) { }, { description: "Malformed imp.ext", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`malformed`)}, }, expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", }, { description: "Malformed imp.ext.prebid", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": malformed}`)}, }, expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", }, { description: "Malformed imp.ext.prebid.bidder", - givenImps: []openrtb.Imp{ + givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": malformed}}`)}, }, expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", @@ -594,7 +594,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.Ext = test.reqExt - req.Regs = &openrtb.Regs{ + req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), } @@ -664,7 +664,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.Ext = test.reqExt - req.Regs = &openrtb.Regs{Ext: test.reqRegsExt} + req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} var reqExtStruct openrtb_ext.ExtRequest err := json.Unmarshal(req.Ext, &reqExtStruct) @@ -713,7 +713,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) - req.Regs = &openrtb.Regs{COPPA: test.coppa} + req.Regs = &openrtb2.Regs{COPPA: test.coppa} auctionReq := AuctionRequest{ BidRequest: req, @@ -847,13 +847,13 @@ func TestExtractBidRequestExt(t *testing.T) { testCases := []struct { desc string - inBidRequest *openrtb.BidRequest + inBidRequest *openrtb2.BidRequest outRequestExt *openrtb_ext.ExtRequest outError error }{ { desc: "Valid vastxml.returnCreative set to false", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":false}}}}`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":false}}}}`)}, outRequestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ Debug: true, @@ -868,7 +868,7 @@ func TestExtractBidRequestExt(t *testing.T) { }, { desc: "Valid vastxml.returnCreative set to true", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":true}}}}`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":true}}}}`)}, outRequestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ Debug: true, @@ -889,13 +889,13 @@ func TestExtractBidRequestExt(t *testing.T) { }, { desc: "Non-nil bidRequest with empty Ext, we expect a blank requestExt", - inBidRequest: &openrtb.BidRequest{}, + inBidRequest: &openrtb2.BidRequest{}, outRequestExt: &openrtb_ext.ExtRequest{}, outError: nil, }, { desc: "Non-nil bidRequest with non-empty, invalid Ext, we expect unmarshaling error", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`invalid`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`invalid`)}, outRequestExt: &openrtb_ext.ExtRequest{}, outError: fmt.Errorf("Error decoding Request.ext : invalid character 'i' looking for beginning of value"), }, @@ -1263,7 +1263,7 @@ func TestGetExtTargetData(t *testing.T) { func TestGetDebugInfo(t *testing.T) { type inTest struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest requestExt *openrtb_ext.ExtRequest } testCases := []struct { @@ -1278,7 +1278,7 @@ func TestGetDebugInfo(t *testing.T) { }, { desc: "bid request test == 0, nil requestExt", - in: inTest{&openrtb.BidRequest{Test: 0}, nil}, + in: inTest{&openrtb2.BidRequest{Test: 0}, nil}, out: false, }, { @@ -1288,22 +1288,22 @@ func TestGetDebugInfo(t *testing.T) { }, { desc: "bid request test == 0, requestExt debug flag false", - in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + in: inTest{&openrtb2.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, out: false, }, { desc: "bid request test == 1, requestExt debug flag false", - in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + in: inTest{&openrtb2.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, out: true, }, { desc: "bid request test == 0, requestExt debug flag true", - in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + in: inTest{&openrtb2.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, out: true, }, { desc: "bid request test == 1, requestExt debug flag true", - in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + in: inTest{&openrtb2.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, out: true, }, } @@ -1593,7 +1593,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) - req.Regs = &openrtb.Regs{ + req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`), } @@ -1646,17 +1646,17 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { } // newAdapterAliasBidRequest builds a BidRequest with aliases -func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { +func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) - return &openrtb.BidRequest{ - Site: &openrtb.Site{ + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ DIDMD5: "some device ID hash", UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", IFA: "ifa", @@ -1664,21 +1664,21 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { DNT: &dnt, Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1692,35 +1692,35 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { } } -func newBidRequest(t *testing.T) *openrtb.BidRequest { - return &openrtb.BidRequest{ - Site: &openrtb.Site{ +func newBidRequest(t *testing.T) *openrtb2.BidRequest { + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ DIDMD5: "some device ID hash", UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", IFA: "ifa", IP: "132.173.230.74", Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Yob: 1982, Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1969,8 +1969,8 @@ func TestRemoveUnpermissionedEids(t *testing.T) { } for _, test := range testCases { - request := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.userExt}, + request := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.userExt}, } requestExt := &openrtb_ext.ExtRequest{ @@ -1981,8 +1981,8 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, } - expectedRequest := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.expectedUserExt}, + expectedRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.expectedUserExt}, } resultErr := removeUnpermissionedEids(request, bidder, requestExt) @@ -2015,8 +2015,8 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { } for _, test := range testCases { - request := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.userExt}, + request := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.userExt}, } requestExt := &openrtb_ext.ExtRequest{ @@ -2037,12 +2037,12 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest requestExt *openrtb_ext.ExtRequest }{ { description: "Nil User", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ User: nil, }, requestExt: &openrtb_ext.ExtRequest{ @@ -2057,8 +2057,8 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { }, { description: "Empty User", - request: &openrtb.BidRequest{ - User: &openrtb.User{}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{}, }, requestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ @@ -2072,15 +2072,15 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { }, { description: "Nil Ext", - request: &openrtb.BidRequest{ - User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, requestExt: nil, }, { description: "Nil Prebid Data", - request: &openrtb.BidRequest{ - User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, requestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ diff --git a/go.mod b/go.mod index 48fc6b6479b..67f95057bee 100644 --- a/go.mod +++ b/go.mod @@ -23,19 +23,16 @@ require ( github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect - github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.0.0 github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.0.0 // indirect - github.com/mssola/user_agent v0.4.1 // indirect - github.com/mxmCherry/openrtb v11.0.0+incompatible + github.com/mxmCherry/openrtb/v14 v14.0.0 github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/prebid/go-gdpr v0.8.3 - github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf // indirect github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect diff --git a/go.sum b/go.sum index 6da3f8898ba..f9c9bb887da 100644 --- a/go.sum +++ b/go.sum @@ -48,11 +48,6 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -65,13 +60,13 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mssola/user_agent v0.4.1 h1:iTUaMpVrb2qWyvUw8UvK3ygWMd2lB1NGuZ1xhpBf1eg= -github.com/mssola/user_agent v0.4.1/go.mod h1:UFiKPVaShrJGW93n4uo8dpPdg1BSVpw2P9bneo0Mtp8= -github.com/mxmCherry/openrtb v11.0.0+incompatible h1:tNzh7vKwQ8lopBAadyN3QPryawXSaVXYWi1IVluXHiM= -github.com/mxmCherry/openrtb v11.0.0+incompatible/go.mod h1:zpnz6Au3bzTGplpRU0kvFPNT6g4ROAKx/GkrslFDwZk= +github.com/mxmCherry/openrtb/v14 v14.0.0 h1:7CUpdQi6Hqi9k03AaUAZVYF3Kqt6ZXryBUFQv8IA/N8= +github.com/mxmCherry/openrtb/v14 v14.0.0/go.mod h1:aAj4RuDpol+zMEVyKiDqiXPHfXevLVmyAf3f6BkRaJw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= @@ -80,12 +75,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prebid/go-gdpr v0.8.3 h1:rjCZNV0AdKygiGHpVhNB42usjEpTN3qidXUPB1yarb0= github.com/prebid/go-gdpr v0.8.3/go.mod h1:TGzgqQDGKOVUkbqmY25K4uvcwMywSddXEaY4zUFiVBQ= -github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs= -github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= -github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= @@ -136,7 +127,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index 09edf51d0db..16b66fc20fc 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -1,8 +1,6 @@ package openrtb_ext -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" type BidRequestVideo struct { // Attribute: @@ -27,7 +25,7 @@ type BidRequestVideo struct { // object; App or Site required // Description: // Application where the impression will be shown - App *openrtb.App `json:"app"` + App *openrtb2.App `json:"app"` // Attribute: // site @@ -35,7 +33,7 @@ type BidRequestVideo struct { // object; App or Site required // Description: // Site where the impression will be shown - Site *openrtb.Site `json:"site"` + Site *openrtb2.Site `json:"site"` // Attribute: // user @@ -43,7 +41,7 @@ type BidRequestVideo struct { // object; optional // Description: // Container object for the user of of the actual device - User *openrtb.User `json:"user,omitempty"` + User *openrtb2.User `json:"user,omitempty"` // Attribute: // device @@ -51,7 +49,7 @@ type BidRequestVideo struct { // object; optional // Description: // Device specific data - Device openrtb.Device `json:"device,omitempty"` + Device openrtb2.Device `json:"device,omitempty"` // Attribute: // includebrandcategory @@ -67,7 +65,7 @@ type BidRequestVideo struct { // object; required // Description: // Player container object - Video *openrtb.Video `json:"video,omitempty"` + Video *openrtb2.Video `json:"video,omitempty"` // Attribute: // content @@ -75,7 +73,7 @@ type BidRequestVideo struct { // object; optional // Description: // Misc content meta data that can be used for targeting the adPod(s) - Content openrtb.Content `json:"content,omitempty"` + Content openrtb2.Content `json:"content,omitempty"` // Attribute: // cacheconfig @@ -135,7 +133,7 @@ type BidRequestVideo struct { // object; optional // Description: // Contains the OpenRTB Regs object to be passed to OpenRTB request - Regs *openrtb.Regs `json:"regs,omitempty"` + Regs *openrtb2.Regs `json:"regs,omitempty"` // Attribute: // supportdeals diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index 8583c053e8f..66236e9477d 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "encoding/json" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" ) // DealTier defines the configuration of a deal tier. @@ -20,7 +20,7 @@ type DealTier struct { type DealTierBidderMap map[BidderName]DealTier // ReadDealTiersFromImp returns a map of bidder deal tiers read from the impression of an original request (not split / cleaned). -func ReadDealTiersFromImp(imp openrtb.Imp) (DealTierBidderMap, error) { +func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { dealTiers := make(DealTierBidderMap) if len(imp.Ext) == 0 { diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 1bb4d5648f0..607956ecb38 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) @@ -83,7 +83,7 @@ func TestReadDealTiersFromImp(t *testing.T) { } for _, test := range testCases { - imp := openrtb.Imp{Ext: test.impExt} + imp := openrtb2.Imp{Ext: test.impExt} result, err := ReadDealTiersFromImp(imp) diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index d517704f44d..d61ec54a887 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -1,8 +1,6 @@ package openrtb_ext -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" // ExtBidResponse defines the contract for bidresponse.ext type ExtBidResponse struct { @@ -27,7 +25,7 @@ type ExtResponseDebug struct { // HttpCalls defines the contract for bidresponse.ext.debug.httpcalls HttpCalls map[BidderName][]*ExtHttpCall `json:"httpcalls,omitempty"` // Request after resolution of stored requests and debug overrides - ResolvedRequest *openrtb.BidRequest `json:"resolvedrequest,omitempty"` + ResolvedRequest *openrtb2.BidRequest `json:"resolvedrequest,omitempty"` } // ExtResponseSyncData defines the contract for bidresponse.ext.usersync.{bidder} diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index 30f8bd25c0d..795cfaf9533 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/cache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" @@ -20,7 +21,6 @@ import ( "github.com/blang/semver" "github.com/buger/jsonparser" "github.com/golang/glog" - "github.com/mxmCherry/openrtb" "golang.org/x/net/publicsuffix" ) @@ -84,18 +84,18 @@ type PBSVideo struct { } type AdUnit struct { - Code string `json:"code"` - TopFrame int8 `json:"is_top_frame"` - Sizes []openrtb.Format `json:"sizes"` - Bids []Bids `json:"bids"` - ConfigID string `json:"config_id"` - MediaTypes []string `json:"media_types"` - Instl int8 `json:"instl"` - Video PBSVideo `json:"video"` + Code string `json:"code"` + TopFrame int8 `json:"is_top_frame"` + Sizes []openrtb2.Format `json:"sizes"` + Bids []Bids `json:"bids"` + ConfigID string `json:"config_id"` + MediaTypes []string `json:"media_types"` + Instl int8 `json:"instl"` + Video PBSVideo `json:"video"` } type PBSAdUnit struct { - Sizes []openrtb.Format + Sizes []openrtb2.Format TopFrame int8 Code string BidID string @@ -153,27 +153,27 @@ func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { } type PBSRequest struct { - AccountID string `json:"account_id"` - Tid string `json:"tid"` - CacheMarkup int8 `json:"cache_markup"` - SortBids int8 `json:"sort_bids"` - MaxKeyLength int8 `json:"max_key_length"` - Secure int8 `json:"secure"` - TimeoutMillis int64 `json:"timeout_millis"` - AdUnits []AdUnit `json:"ad_units"` - IsDebug bool `json:"is_debug"` - App *openrtb.App `json:"app"` - Device *openrtb.Device `json:"device"` - PBSUser json.RawMessage `json:"user"` - SDK *SDK `json:"sdk"` + AccountID string `json:"account_id"` + Tid string `json:"tid"` + CacheMarkup int8 `json:"cache_markup"` + SortBids int8 `json:"sort_bids"` + MaxKeyLength int8 `json:"max_key_length"` + Secure int8 `json:"secure"` + TimeoutMillis int64 `json:"timeout_millis"` + AdUnits []AdUnit `json:"ad_units"` + IsDebug bool `json:"is_debug"` + App *openrtb2.App `json:"app"` + Device *openrtb2.Device `json:"device"` + PBSUser json.RawMessage `json:"user"` + SDK *SDK `json:"sdk"` // internal Bidders []*PBSBidder `json:"-"` - User *openrtb.User `json:"-"` + User *openrtb2.User `json:"-"` Cookie *usersync.PBSCookie `json:"-"` Url string `json:"-"` Domain string `json:"-"` - Regs *openrtb.Regs `json:"-"` + Regs *openrtb2.Regs `json:"-"` Start time.Time } @@ -236,7 +236,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) if pbsReq.Device == nil { - pbsReq.Device = &openrtb.Device{} + pbsReq.Device = &openrtb2.Device{} } if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { pbsReq.Device.IP = ip.String() @@ -259,7 +259,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C } if pbsReq.User == nil { - pbsReq.User = &openrtb.User{} + pbsReq.User = &openrtb2.User{} } // use client-side data for web requests diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go index 7c6cea31261..b8cf2c19ff7 100644 --- a/pbs/pbsresponse.go +++ b/pbs/pbsresponse.go @@ -32,9 +32,9 @@ type PBSBid struct { // If NURL and Adm are both defined, then Adm takes precedence. Adm string `json:"adm,omitempty"` // Width is the intended width which Adm should be shown, in pixels. - Width uint64 `json:"width,omitempty"` + Width int64 `json:"width,omitempty"` // Height is the intended width which Adm should be shown, in pixels. - Height uint64 `json:"height,omitempty"` + Height int64 `json:"height,omitempty"` // DealId is not used by prebid-server, but may be used by buyers and sellers who make special // deals with each other. We simply pass this information along with the bid. DealId string `json:"deal_id,omitempty"` diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 88c197cd0b8..730d54b0acb 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -22,7 +22,7 @@ import ( // Client stores values in Prebid Cache. For more info, see https://github.com/prebid/prebid-cache type Client interface { - // PutJson stores JSON values for the given openrtb.Bids in the cache. Null values will be + // PutJson stores JSON values for the given openrtb2.Bids in the cache. Null values will be // // The returned string slice will always have the same number of elements as the values argument. If a // value could not be saved, the element will be an empty string. Implementations are responsible for diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go index c2f682c6129..cde7ec8d951 100644 --- a/prebid_cache_client/prebid_cache.go +++ b/prebid_cache_client/prebid_cache.go @@ -22,8 +22,8 @@ type CacheObject struct { type BidCache struct { Adm string `json:"adm,omitempty"` NURL string `json:"nurl,omitempty"` - Width uint64 `json:"width,omitempty"` - Height uint64 `json:"height,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` } // internal protocol objects diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 4856655402b..67b5681deca 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,8 +1,6 @@ package ccpa -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" // ConsentWriter implements the PolicyWriter interface for CCPA. type ConsentWriter struct { @@ -10,7 +8,7 @@ type ConsentWriter struct { } // Write mutates an OpenRTB bid request with the CCPA consent string. -func (c ConsentWriter) Write(req *openrtb.BidRequest) error { +func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 57a7f8f4ddf..d805936cf72 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,8 +12,8 @@ func TestConsentWriter(t *testing.T) { consent := "anyConsent" testCases := []struct { description string - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { @@ -23,19 +23,19 @@ func TestConsentWriter(t *testing.T) { }, { description: "Success", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, }, { description: "Error With Regs.Ext - Does Not Mutate", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, expectedError: true, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, }, } diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 2f7e8bfd683..183a774363c 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,7 +3,7 @@ package ccpa import ( "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -385,7 +385,7 @@ type mockPolicWriter struct { mock.Mock } -func (m *mockPolicWriter) Write(req *openrtb.BidRequest) error { +func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { args := m.Called(req) return args.Error(0) } diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index a9f1c49e47d..64250375106 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -16,7 +16,7 @@ type Policy struct { } // ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request. -func ReadFromRequest(req *openrtb.BidRequest) (Policy, error) { +func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { var consent string var noSaleBidders []string @@ -46,7 +46,7 @@ func ReadFromRequest(req *openrtb.BidRequest) (Policy, error) { } // Write mutates an OpenRTB bid request with the CCPA regulatory information. -func (p Policy) Write(req *openrtb.BidRequest) error { +func (p Policy) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } @@ -65,14 +65,14 @@ func (p Policy) Write(req *openrtb.BidRequest) error { return nil } -func buildRegs(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegs(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { if consent == "" { return buildRegsClear(regs) } return buildRegsWrite(consent, regs) } -func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegsClear(regs *openrtb2.Regs) (*openrtb2.Regs, error) { if regs == nil || len(regs.Ext) == 0 { return regs, nil } @@ -92,7 +92,7 @@ func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { } // Marshal ext if there are still other fields - var regsResult openrtb.Regs + var regsResult openrtb2.Regs ext, err := json.Marshal(extMap) if err == nil { regsResult = *regs @@ -101,9 +101,9 @@ func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { return ®sResult, err } -func buildRegsWrite(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegsWrite(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { if regs == nil { - return marshalRegsExt(openrtb.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) + return marshalRegsExt(openrtb2.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) } if regs.Ext == nil { @@ -119,7 +119,7 @@ func buildRegsWrite(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { return marshalRegsExt(*regs, extMap) } -func marshalRegsExt(regs openrtb.Regs, ext interface{}) (*openrtb.Regs, error) { +func marshalRegsExt(regs openrtb2.Regs, ext interface{}) (*openrtb2.Regs, error) { extJSON, err := json.Marshal(ext) if err == nil { regs.Ext = extJSON diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 7ff896e9ebf..31eca2cca57 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -4,21 +4,21 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) func TestReadFromRequest(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest expectedPolicy Policy expectedError bool }{ { description: "Success", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -36,7 +36,7 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Regs", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Regs: nil, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, @@ -47,8 +47,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -58,8 +58,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Empty Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -69,8 +69,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Missing Regs.Ext USPrivacy Value", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"anythingElse":"42"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"anythingElse":"42"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -80,24 +80,24 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Malformed Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedError: true, }, { description: "Invalid Regs.Ext Type", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedError: true, }, { description: "Nil Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: nil, }, expectedPolicy: Policy{ @@ -107,8 +107,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Empty Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{}`), }, expectedPolicy: Policy{ @@ -118,8 +118,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Missing Ext.Prebid No Sale Value", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"anythingElse":"42"}`), }, expectedPolicy: Policy{ @@ -129,24 +129,24 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Malformed Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`malformed`), }, expectedError: true, }, { description: "Invalid Ext.Prebid.NoSale Type", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":"wrongtype"}}`), }, expectedError: true, }, { description: "Injection Attack", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, }, expectedPolicy: Policy{ Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", @@ -165,8 +165,8 @@ func TestWrite(t *testing.T) { testCases := []struct { description string policy Policy - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { @@ -178,31 +178,31 @@ func TestWrite(t *testing.T) { { description: "Success", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, }, { description: "Error Regs.Ext - No Partial Update To Request", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, expectedError: true, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, }, { description: "Error Ext - No Partial Update To Request", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Ext: json.RawMessage(`malformed}`), }, expectedError: true, - expected: &openrtb.BidRequest{ + expected: &openrtb2.BidRequest{ Ext: json.RawMessage(`malformed}`), }, }, @@ -219,22 +219,22 @@ func TestBuildRegs(t *testing.T) { testCases := []struct { description string consent string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { description: "Clear", consent: "", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"ABC"}`), }, - expected: &openrtb.Regs{}, + expected: &openrtb2.Regs{}, }, { description: "Clear - Error", consent: "", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, expectedError: true, @@ -243,14 +243,14 @@ func TestBuildRegs(t *testing.T) { description: "Write", consent: "anyConsent", regs: nil, - expected: &openrtb.Regs{ + expected: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`), }, }, { description: "Write - Error", consent: "anyConsent", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, expectedError: true, @@ -267,8 +267,8 @@ func TestBuildRegs(t *testing.T) { func TestBuildRegsClear(t *testing.T) { testCases := []struct { description string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { @@ -278,32 +278,32 @@ func TestBuildRegsClear(t *testing.T) { }, { description: "Nil Regs.Ext", - regs: &openrtb.Regs{Ext: nil}, - expected: &openrtb.Regs{Ext: nil}, + regs: &openrtb2.Regs{Ext: nil}, + expected: &openrtb2.Regs{Ext: nil}, }, { description: "Empty Regs.Ext", - regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb2.Regs{}, }, { description: "Removes Regs.Ext Entirely", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb2.Regs{}, }, { description: "Leaves Other Regs.Ext Values", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC", "other":"any"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC", "other":"any"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, }, { description: "Invalid Regs.Ext Type - Still Cleared", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{}, }, { description: "Malformed Regs.Ext", - regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } @@ -319,50 +319,50 @@ func TestBuildRegsWrite(t *testing.T) { testCases := []struct { description string consent string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { description: "Nil Regs", consent: "anyConsent", regs: nil, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Nil Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: nil}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: nil}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Empty Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Overwrites Existing", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Leaves Other Ext Values", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, }, { description: "Invalid Regs.Ext Type - Still Overwrites", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Malformed Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 64070ae3a6a..4b946966206 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -1,8 +1,6 @@ package privacy -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { @@ -19,11 +17,11 @@ func (e Enforcement) Any() bool { } // Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb.BidRequest) { +func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest) { e.apply(bidRequest, NewScrubber()) } -func (e Enforcement) apply(bidRequest *openrtb.BidRequest, scrubber Scrubber) { +func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { if bidRequest != nil && e.Any() { bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 9240aafc2c3..5080709f2cb 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -3,7 +3,7 @@ package privacy import ( "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -197,12 +197,12 @@ func TestApply(t *testing.T) { } for _, test := range testCases { - req := &openrtb.BidRequest{ - Device: &openrtb.Device{}, - User: &openrtb.User{}, + req := &openrtb2.BidRequest{ + Device: &openrtb2.Device{}, + User: &openrtb2.User{}, } - replacedDevice := &openrtb.Device{} - replacedUser := &openrtb.User{} + replacedDevice := &openrtb2.Device{} + replacedUser := &openrtb2.User{} m := &mockScrubber{} m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() @@ -217,7 +217,7 @@ func TestApply(t *testing.T) { } func TestApplyNoneApplicable(t *testing.T) { - req := &openrtb.BidRequest{} + req := &openrtb2.BidRequest{} m := &mockScrubber{} @@ -248,12 +248,12 @@ type mockScrubber struct { mock.Mock } -func (m *mockScrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +func (m *mockScrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { args := m.Called(device, id, ipv4, ipv6, geo) - return args.Get(0).(*openrtb.Device) + return args.Get(0).(*openrtb2.Device) } -func (m *mockScrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { +func (m *mockScrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { args := m.Called(user, strategy, geo) - return args.Get(0).(*openrtb.User) + return args.Get(0).(*openrtb2.User) } diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 040bbd6c94b..53885e51d49 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -3,9 +3,8 @@ package gdpr import ( "encoding/json" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. @@ -14,13 +13,13 @@ type ConsentWriter struct { } // Write mutates an OpenRTB bid request with the GDPR TCF consent. -func (c ConsentWriter) Write(req *openrtb.BidRequest) error { +func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if c.Consent == "" { return nil } if req.User == nil { - req.User = &openrtb.User{} + req.User = &openrtb2.User{} } if req.User.Ext == nil { diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index 77fbdf88d92..9b23dccc6e4 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,76 +12,76 @@ func TestConsentWriter(t *testing.T) { testCases := []struct { description string consent string - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { description: "Empty", consent: "", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{}, }, { description: "Enabled With Nil Request User Object", consent: "anyConsent", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, }, { description: "Enabled With Nil Request User Ext Object", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{}}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Overwrites", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, }, { description: "Enabled With Existing Malformed Request User Ext Object", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`malformed`)}}, expectedError: true, }, { description: "Injection Attack With Nil Request User Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), }}, }, { description: "Injection Attack With Nil Request User Ext Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{}}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), }}, }, { description: "Injection Attack With Existing Request User Ext Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`), }}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), }}, }, diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index cac9418643e..07bd7ce0a70 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -3,7 +3,7 @@ package lmt import ( "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/iosutil" ) @@ -14,7 +14,7 @@ var ( ) // ModifyForIOS modifies the request's LMT flag based on iOS version and identity. -func ModifyForIOS(req *openrtb.BidRequest) { +func ModifyForIOS(req *openrtb2.BidRequest) { modifiers := map[iosutil.VersionClassification]modifier{ iosutil.Version140: modifyForIOS14X, iosutil.Version141: modifyForIOS14X, @@ -23,7 +23,7 @@ func ModifyForIOS(req *openrtb.BidRequest) { modifyForIOS(req, modifiers) } -func modifyForIOS(req *openrtb.BidRequest, modifiers map[iosutil.VersionClassification]modifier) { +func modifyForIOS(req *openrtb2.BidRequest, modifiers map[iosutil.VersionClassification]modifier) { if !isRequestForIOS(req) { return } @@ -34,13 +34,13 @@ func modifyForIOS(req *openrtb.BidRequest, modifiers map[iosutil.VersionClassifi } } -func isRequestForIOS(req *openrtb.BidRequest) bool { +func isRequestForIOS(req *openrtb2.BidRequest) bool { return req != nil && req.App != nil && req.Device != nil && strings.EqualFold(req.Device.OS, "ios") } -type modifier func(req *openrtb.BidRequest) +type modifier func(req *openrtb2.BidRequest) -func modifyForIOS14X(req *openrtb.BidRequest) { +func modifyForIOS14X(req *openrtb2.BidRequest) { if req.Device.IFA == "" || req.Device.IFA == "00000000-0000-0000-0000-000000000000" { req.Device.Lmt = &int8One } else { @@ -48,7 +48,7 @@ func modifyForIOS14X(req *openrtb.BidRequest) { } } -func modifyForIOS142OrGreater(req *openrtb.BidRequest) { +func modifyForIOS142OrGreater(req *openrtb2.BidRequest) { atts, err := openrtb_ext.ParseDeviceExtATTS(req.Device.Ext) if err != nil || atts == nil { return diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 1c860be33bd..5071adf4d04 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/util/iosutil" "github.com/stretchr/testify/assert" ) @@ -13,24 +13,24 @@ import ( func TestModifyForIOS(t *testing.T) { testCases := []struct { description string - givenRequest *openrtb.BidRequest + givenRequest *openrtb2.BidRequest expectedLMT *int8 }{ { description: "13.0", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS", OSV: "13.0", IFA: "", Lmt: nil}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "13.0", IFA: "", Lmt: nil}, }, expectedLMT: nil, }, { description: "14.0", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS", OSV: "14.0", IFA: "", Lmt: nil}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.0", IFA: "", Lmt: nil}, }, - expectedLMT: openrtb.Int8Ptr(1), + expectedLMT: openrtb2.Int8Ptr(1), }, } @@ -43,50 +43,50 @@ func TestModifyForIOS(t *testing.T) { func TestModifyForIOSHelper(t *testing.T) { testCases := []struct { description string - givenRequest *openrtb.BidRequest + givenRequest *openrtb2.BidRequest expectedModifier140Called bool expectedModifier142Called bool }{ { description: "Valid 14.0", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS", OSV: "14.0"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.0"}, }, expectedModifier140Called: true, expectedModifier142Called: false, }, { description: "Valid 14.2 Or Greater", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS", OSV: "14.2"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.2"}, }, expectedModifier140Called: false, expectedModifier142Called: true, }, { description: "Invalid Version", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS", OSV: "invalid"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "invalid"}, }, expectedModifier140Called: false, expectedModifier142Called: false, }, { description: "Invalid OS", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "invalid", OSV: "14.0"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "invalid", OSV: "14.0"}, }, expectedModifier140Called: false, expectedModifier142Called: false, }, { description: "Invalid Nil Device", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, Device: nil, }, expectedModifier140Called: false, @@ -96,10 +96,10 @@ func TestModifyForIOSHelper(t *testing.T) { for _, test := range testCases { modifierIOS140Called := false - modifierIOS140 := func(req *openrtb.BidRequest) { modifierIOS140Called = true } + modifierIOS140 := func(req *openrtb2.BidRequest) { modifierIOS140Called = true } modifierIOS142Called := false - modifierIOS142 := func(req *openrtb.BidRequest) { modifierIOS142Called = true } + modifierIOS142 := func(req *openrtb2.BidRequest) { modifierIOS142Called = true } modifiers := map[iosutil.VersionClassification]modifier{ iosutil.Version140: modifierIOS140, @@ -116,22 +116,22 @@ func TestModifyForIOSHelper(t *testing.T) { func TestIsRequestForIOS(t *testing.T) { testCases := []struct { description string - givenRequest *openrtb.BidRequest + givenRequest *openrtb2.BidRequest expected bool }{ { description: "Valid", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "iOS"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS"}, }, expected: true, }, { description: "Valid - OS Case Insensitive", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "IOS"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "IOS"}, }, expected: true, }, @@ -142,33 +142,33 @@ func TestIsRequestForIOS(t *testing.T) { }, { description: "Invalid - Nil App", - givenRequest: &openrtb.BidRequest{ + givenRequest: &openrtb2.BidRequest{ App: nil, - Device: &openrtb.Device{OS: "iOS"}, + Device: &openrtb2.Device{OS: "iOS"}, }, expected: false, }, { description: "Invalid - Nil Device", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, Device: nil, }, expected: false, }, { description: "Invalid - Empty OS", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: ""}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: ""}, }, expected: false, }, { description: "Invalid - Wrong OS", - givenRequest: &openrtb.BidRequest{ - App: &openrtb.App{}, - Device: &openrtb.Device{OS: "Android"}, + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "Android"}, }, expected: false, }, @@ -183,33 +183,33 @@ func TestIsRequestForIOS(t *testing.T) { func TestModifyForIOS14X(t *testing.T) { testCases := []struct { description string - givenDevice openrtb.Device + givenDevice openrtb2.Device expectedLMT *int8 }{ { description: "IFA Empty", - givenDevice: openrtb.Device{IFA: "", Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{IFA: "", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), }, { description: "IFA Zero UUID", - givenDevice: openrtb.Device{IFA: "00000000-0000-0000-0000-000000000000", Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{IFA: "00000000-0000-0000-0000-000000000000", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), }, { description: "IFA Populated", - givenDevice: openrtb.Device{IFA: "any-real-value", Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(0), + givenDevice: openrtb2.Device{IFA: "any-real-value", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), }, { description: "Overwrites Existing", - givenDevice: openrtb.Device{IFA: "", Lmt: openrtb.Int8Ptr(0)}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{IFA: "", Lmt: openrtb2.Int8Ptr(0)}, + expectedLMT: openrtb2.Int8Ptr(1), }, } for _, test := range testCases { - request := &openrtb.BidRequest{Device: &test.givenDevice} + request := &openrtb2.BidRequest{Device: &test.givenDevice} modifyForIOS14X(request) assert.Equal(t, test.expectedLMT, request.Device.Lmt, test.description) } @@ -218,58 +218,58 @@ func TestModifyForIOS14X(t *testing.T) { func TestModifyForIOS142OrGreater(t *testing.T) { testCases := []struct { description string - givenDevice openrtb.Device + givenDevice openrtb2.Device expectedLMT *int8 }{ { description: "Not Determined", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":0}`), Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(0), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":0}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), }, { description: "Restricted", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":1}`), Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":1}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), }, { description: "Denied", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":2}`), Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":2}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), }, { description: "Authorized", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: nil}, - expectedLMT: openrtb.Int8Ptr(0), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), }, { description: "Overwrites Existing", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: openrtb.Int8Ptr(1)}, - expectedLMT: openrtb.Int8Ptr(0), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: openrtb2.Int8Ptr(1)}, + expectedLMT: openrtb2.Int8Ptr(0), }, { description: "Invalid Value - Unknown", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: nil}, + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: nil}, expectedLMT: nil, }, { description: "Invalid Value - Unknown - Does Not Overwrite Existing", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: openrtb.Int8Ptr(1)}, - expectedLMT: openrtb.Int8Ptr(1), + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: openrtb2.Int8Ptr(1)}, + expectedLMT: openrtb2.Int8Ptr(1), }, { description: "Invalid Value - Missing", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{}`), Lmt: nil}, + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{}`), Lmt: nil}, expectedLMT: nil, }, { description: "Invalid Value - Wrong Type", - givenDevice: openrtb.Device{Ext: json.RawMessage(`{"atts":"wrong type"}`), Lmt: nil}, + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":"wrong type"}`), Lmt: nil}, expectedLMT: nil, }, } for _, test := range testCases { - request := &openrtb.BidRequest{Device: &test.givenDevice} + request := &openrtb2.BidRequest{Device: &test.givenDevice} modifyForIOS142OrGreater(request) assert.Equal(t, test.expectedLMT, request.Device.Lmt, test.description) } diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index 295dcc46469..e115dc3802f 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -1,8 +1,6 @@ package lmt -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" const ( trackingUnrestricted = 0 @@ -16,7 +14,7 @@ type Policy struct { } // ReadFromRequest extracts the LMT (Limit Ad Tracking) policy from an OpenRTB bid request. -func ReadFromRequest(req *openrtb.BidRequest) (policy Policy) { +func ReadFromRequest(req *openrtb2.BidRequest) (policy Policy) { if req != nil && req.Device != nil && req.Device.Lmt != nil { policy.Signal = int(*req.Device.Lmt) policy.SignalProvided = true diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index 3027414fd02..e36f30230d8 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -3,7 +3,7 @@ package lmt import ( "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,7 +12,7 @@ func TestReadFromRequest(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest expectedPolicy Policy }{ { @@ -25,7 +25,7 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Device", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Device: nil, }, expectedPolicy: Policy{ @@ -35,8 +35,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Device.Lmt", - request: &openrtb.BidRequest{ - Device: &openrtb.Device{ + request: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ Lmt: nil, }, }, @@ -47,8 +47,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Enabled", - request: &openrtb.BidRequest{ - Device: &openrtb.Device{ + request: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ Lmt: &one, }, }, diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 8771c8b3282..d656168ae12 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" ) // ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. @@ -73,8 +73,8 @@ const ( // Scrubber removes PII from parts of an OpenRTB request. type Scrubber interface { - ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device - ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User + ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device + ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User } type scrubber struct{} @@ -84,7 +84,7 @@ func NewScrubber() Scrubber { return scrubber{} } -func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +func (scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { if device == nil { return nil } @@ -124,7 +124,7 @@ func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ip return &deviceCopy } -func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { +func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { if user == nil { return nil } @@ -194,15 +194,15 @@ func removeLowestIPV6Segment(ip string) string { return ip[0:i] } -func scrubGeoFull(geo *openrtb.Geo) *openrtb.Geo { +func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { if geo == nil { return nil } - return &openrtb.Geo{} + return &openrtb2.Geo{} } -func scrubGeoPrecision(geo *openrtb.Geo) *openrtb.Geo { +func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { if geo == nil { return nil } diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index e0a2cb86f64..2d352e71821 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -4,12 +4,12 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) func TestScrubDevice(t *testing.T) { - device := &openrtb.Device{ + device := &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -19,7 +19,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -30,7 +30,7 @@ func TestScrubDevice(t *testing.T) { testCases := []struct { description string - expected *openrtb.Device + expected *openrtb2.Device id ScrubStrategyDeviceID ipv4 ScrubStrategyIPV4 ipv6 ScrubStrategyIPV6 @@ -46,7 +46,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "All Strageties - Strictest", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "", DIDSHA1: "", DPIDMD5: "", @@ -56,7 +56,7 @@ func TestScrubDevice(t *testing.T) { IFA: "", IP: "1.2.3.0", IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDAll, ipv4: ScrubStrategyIPV4Lowest8, @@ -65,7 +65,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - ID - All", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "", DIDSHA1: "", DPIDMD5: "", @@ -84,7 +84,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv4 - Lowest 8", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -103,7 +103,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv6 - Lowest 16", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -122,7 +122,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv6 - Lowest 32", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -141,7 +141,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - Geo - Reduced Precision", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -151,7 +151,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -166,7 +166,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - Geo - Full", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -176,7 +176,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDNone, ipv4: ScrubStrategyIPV4None, @@ -197,13 +197,13 @@ func TestScrubDeviceNil(t *testing.T) { } func TestScrubUser(t *testing.T) { - user := &openrtb.User{ + user := &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -214,32 +214,32 @@ func TestScrubUser(t *testing.T) { testCases := []struct { description string - expected *openrtb.User + expected *openrtb2.User scrubUser ScrubStrategyUser scrubGeo ScrubStrategyGeo }{ { description: "User ID And Demographic & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserIDAndDemographic, scrubGeo: ScrubStrategyGeoFull, }, { description: "User ID And Demographic & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -252,13 +252,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID And Demographic & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -271,26 +271,26 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserID, scrubGeo: ScrubStrategyGeoFull, }, { description: "User ID & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -303,13 +303,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -322,26 +322,26 @@ func TestScrubUser(t *testing.T) { }, { description: "User None & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserNone, scrubGeo: ScrubStrategyGeoFull, }, { description: "User None & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -354,13 +354,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User None & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -503,14 +503,14 @@ func TestScrubIPV6Lowest32Bits(t *testing.T) { } func TestScrubGeoFull(t *testing.T) { - geo := &openrtb.Geo{ + geo := &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", City: "some city", ZIP: "some zip", } - geoExpected := &openrtb.Geo{ + geoExpected := &openrtb2.Geo{ Lat: 0, Lon: 0, Metro: "", @@ -529,14 +529,14 @@ func TestScrubGeoFullWhenNil(t *testing.T) { } func TestScrubGeoPrecision(t *testing.T) { - geo := &openrtb.Geo{ + geo := &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", City: "some city", ZIP: "some zip", } - geoExpected := &openrtb.Geo{ + geoExpected := &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", diff --git a/privacy/writer.go b/privacy/writer.go index c61767f16c8..610508f812c 100644 --- a/privacy/writer.go +++ b/privacy/writer.go @@ -1,18 +1,16 @@ package privacy -import ( - "github.com/mxmCherry/openrtb" -) +import "github.com/mxmCherry/openrtb/v14/openrtb2" // PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. type PolicyWriter interface { - Write(req *openrtb.BidRequest) error + Write(req *openrtb2.BidRequest) error } // NilPolicyWriter implements the PolicyWriter interface but performs no action. type NilPolicyWriter struct{} // Write is hardcoded to perform no action with the OpenRTB bid request. -func (NilPolicyWriter) Write(req *openrtb.BidRequest) error { +func (NilPolicyWriter) Write(req *openrtb2.BidRequest) error { return nil } diff --git a/privacy/writer_test.go b/privacy/writer_test.go index 79170cfc451..b419e3c6783 100644 --- a/privacy/writer_test.go +++ b/privacy/writer_test.go @@ -4,16 +4,16 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb" + "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/stretchr/testify/assert" ) func TestNilWriter(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "anyID", Ext: json.RawMessage(`{"anyJson":"anyValue"}`), } - expectedRequest := &openrtb.BidRequest{ + expectedRequest := &openrtb2.BidRequest{ ID: "anyID", Ext: json.RawMessage(`{"anyJson":"anyValue"}`), } diff --git a/router/router.go b/router/router.go index 0920f1d61b6..3b51d0730c1 100644 --- a/router/router.go +++ b/router/router.go @@ -261,7 +261,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) if err != nil { - glog.Fatalf("Failed to create the openrtb endpoint handler. %v", err) + glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) From 690fe2d5c2391b1617ec6d85fb2c15b090c3dd9f Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Tue, 30 Mar 2021 19:31:43 +0200 Subject: [PATCH 364/603] Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes --- adapters/tappx/tappx.go | 7 +- adapters/tappx/tappx_test.go | 2 +- ...ngle-banner-impression-future-feature.json | 116 ++++++++++++++++++ .../exemplary/single-banner-impression.json | 2 +- .../exemplary/single-banner-site.json | 2 +- .../exemplary/single-video-impression.json | 2 +- .../exemplary/single-video-site.json | 2 +- .../tappxtest/supplemental/204status.json | 2 +- .../tappxtest/supplemental/bidfloor.json | 2 +- .../supplemental/http-err-status.json | 2 +- .../supplemental/http-err-status2.json | 2 +- 11 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index abe2d90b8a6..57abee63923 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "text/template" "time" @@ -17,7 +18,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const TAPPX_BIDDER_VERSION = "1.1" +const TAPPX_BIDDER_VERSION = "1.2" const TYPE_CNN = "prebid" type TappxAdapter struct { @@ -126,7 +127,9 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in } } - thisURI.Path += params.Endpoint + if !(strings.Contains(strings.ToLower(thisURI.Host), strings.ToLower(params.Endpoint))) { + thisURI.Path += params.Endpoint //Now version is backward compatible. In future, this condition and content will be delete + } queryParams := url.Values{} diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 187190862ea..10e57d12132 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url) + match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url) if err != nil { t.Errorf("Error while running regex validation: %s", err.Error()) return diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json new file mode 100644 index 00000000000..3c3037afefb --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "ZZ123456PS.ssp.tappx.com/rtb/" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "ZZ123456PS.ssp.tappx.com/rtb/" + } + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index 4f5b792fe4f..54f472d9fff 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json index ef61b0e2567..58490233ede 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -37,7 +37,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index 7a469b07fb6..d6ce0554c5f 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json index 9ee2f8f4187..f151151e776 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 6172da59cbd..1c72cc90f24 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 48c1b0fbfa7..093f77adfc6 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index 045695046ff..a80a5eaa675 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index 0cb5fd4a400..41dcc26d653 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, From 056b7cc357708c61490ab6cfbb1d82b53ea59415 Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Wed, 31 Mar 2021 17:19:03 -0400 Subject: [PATCH 365/603] DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm --- adapters/dmx/dmx.go | 50 ++++-- .../dmx/dmxtest/exemplary/idfa-to-app-id.json | 148 ++++++++++++++++++ .../exemplary/missing-width-height.json | 143 +++++++++++++++++ 3 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json create mode 100644 adapters/dmx/dmxtest/exemplary/missing-width-height.json diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 79e1d751712..280c7187ca8 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -48,6 +48,8 @@ type dmxParams struct { Bidfloor float64 `json:"bidfloor,omitempty"` } +var protocols = []openrtb2.Protocol{2, 3, 5, 6, 7, 8} + func UserSellerOrPubId(str1, str2 string) string { if str1 != "" { return str1 @@ -100,6 +102,12 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt if dmxReq.App.ID != "" { anyHasId = true } + if anyHasId == false { + if idfa, valid := getIdfa(request); valid { + dmxReq.App.ID = idfa + anyHasId = true + } + } } else { dmxReq.App = nil } @@ -147,13 +155,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt } } - if anyHasId == false { - return nil, []error{errors.New("This request contained no identifier")} - } - for _, inst := range dmxReq.Imp { - var banner *openrtb2.Banner - var video *openrtb2.Video var ins openrtb2.Imp var params dmxExt const intVal int8 = 1 @@ -164,9 +166,9 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt if isDmxParams(params.Bidder) { if inst.Banner != nil { if len(inst.Banner.Format) != 0 { - banner = inst.Banner + bannerCopy := *inst.Banner if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { - imps = fetchParams(params, inst, ins, imps, banner, nil, intVal) + imps = fetchParams(params, inst, ins, imps, &bannerCopy, nil, intVal) } else { return nil, []error{errors.New("Missing Params for auction to be send")} } @@ -174,9 +176,9 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt } if inst.Video != nil { - video = inst.Video + videoCopy := *inst.Video if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { - imps = fetchParams(params, inst, ins, imps, nil, video, intVal) + imps = fetchParams(params, inst, ins, imps, nil, &videoCopy, intVal) } else { return nil, []error{errors.New("Missing Params for auction to be send")} } @@ -187,6 +189,10 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt dmxReq.Imp = imps + if anyHasId == false { + return nil, []error{errors.New("This request contained no identifier")} + } + oJson, err := json.Marshal(dmxReq) if err != nil { @@ -271,10 +277,15 @@ func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []open tempimp.Secure = &intVal } if banner != nil { + if banner.H == nil || banner.W == nil { + banner.H = &banner.Format[0].H + banner.W = &banner.Format[0].W + } tempimp.Banner = banner } if video != nil { + video.Protocols = checkProtocols(video) tempimp.Video = video } @@ -327,3 +338,22 @@ func isDmxParams(t interface{}) bool { return false } } + +func getIdfa(request *openrtb2.BidRequest) (string, bool) { + if request.Device == nil { + return "", false + } + + device := request.Device + + if device.IFA != "" { + return device.IFA, true + } + return "", false +} +func checkProtocols(imp *openrtb2.Video) []openrtb2.Protocol { + if len(imp.Protocols) > 0 { + return imp.Protocols + } + return protocols +} diff --git a/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json b/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json new file mode 100644 index 00000000000..2fcf9179796 --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "device": { + "ifa": "ed6207cefff74c14878963566683c070" + }, + "imp": [ + { + + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "dmxid": "123454", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400", + "ext": { + "dmx": { + "id": "10400" + } + } + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "device": { + "ifa": "ed6207cefff74c14878963566683c070" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123454", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisher_id": "10400", + "dmxid": "123454" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adid": "29681110", + "adm": "
banner-ads
", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adm": "
banner-ads
", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/exemplary/missing-width-height.json b/adapters/dmx/dmxtest/exemplary/missing-width-height.json new file mode 100644 index 00000000000..b7eeec68fcf --- /dev/null +++ b/adapters/dmx/dmxtest/exemplary/missing-width-height.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400" + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + "bidfloor": 0.35, + "id": "test-imp-id", + "banner": { + + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "dmxid": "123454", + "publisher_id": "10400" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "", + "body": { + "id": "test-request-id", + "app":{ + "bundle":"302324249", + "id":"ed6207cefff74c14878963566683c070", + "name":"Skout - iOS Match Buy", + "publisher":{ + "id":"10400", + "ext": { + "dmx": { + "id": "10400" + } + } + }, + "storeurl":"https://itunes.apple.com/app/id302324249" + }, + "imp": [ + { + "bidfloor": 0.35, + "id": "test-imp-id", + "tagid": "123454", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisher_id": "10400", + "dmxid": "123454" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adid": "29681110", + "adm": "
banner-ads
", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 1.75, + "adm": "
banner-ads
", + "adid": "29681110", + "adomain": ["dmx.districtm.io"], + "iurl": "https://dmx.districtm.io/b/v2", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file From cbc3e84b1e008d25d914bff91147608a2acf44cd Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 1 Apr 2021 16:34:34 -0400 Subject: [PATCH 366/603] Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict --- exchange/bidder.go | 38 +++--- exchange/bidder_test.go | 255 +++++++++++++++++++++++++++++----------- 2 files changed, 208 insertions(+), 85 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index d6b8028c2e9..5b6da499093 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -167,6 +167,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { if accountDebugAllowed { if bidder.config.DebugInfo.Allow { + // it's safe to mutate the request headers since from this point on the + // information is only used for debugging. + removeSensitiveHeaders(httpInfo.request.Headers) + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) } else { debugDisabledWarning := errortypes.Warning{ @@ -325,25 +329,29 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset return nativeRequests.Asset{}, fmt.Errorf("Unable to find asset with ID:%d in the request", id) } +var authorizationHeader = http.CanonicalHeaderKey("authorization") + +// removeSensitiveHeaders mutates the http header object to remove sensitive information. +func removeSensitiveHeaders(h http.Header) { + h.Del(authorizationHeader) +} + // makeExt transforms information about the HTTP call into the contract class for the PBS response. func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { - if httpInfo.err == nil { - return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), - ResponseBody: string(httpInfo.response.Body), - Status: httpInfo.response.StatusCode, - RequestHeaders: httpInfo.request.Headers, - } - } else if httpInfo.request == nil { - return &openrtb_ext.ExtHttpCall{} - } else { - return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), - RequestHeaders: httpInfo.request.Headers, + ext := &openrtb_ext.ExtHttpCall{} + + if httpInfo != nil && httpInfo.request != nil { + ext.Uri = httpInfo.request.Uri + ext.RequestBody = string(httpInfo.request.Body) + ext.RequestHeaders = httpInfo.request.Headers + + if httpInfo.err == nil && httpInfo.response != nil { + ext.ResponseBody = string(httpInfo.response.Body) + ext.Status = httpInfo.response.StatusCode } } + + return ext } // doRequest makes a request, handles the response, and returns the data needed by the diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 8cc5b5e9383..7c01cd84c83 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -71,7 +71,6 @@ func TestSingleBidder(t *testing.T) { ctx = context.WithValue(ctx, DebugContextKey, true) for _, test := range testCases { - mockBidderResponse := &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { @@ -142,6 +141,48 @@ func TestSingleBidder(t *testing.T) { } } +func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { + server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + requestHeaders.Add("Authorization", "anySecret") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("requestJson"), + Headers: requestHeaders, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{}, + }, + } + + debugInfo := &config.DebugInfo{Allow: true} + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) + + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + + expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ + { + Uri: server.URL, + RequestBody: "requestJson", + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}}, + ResponseBody: "responseJson", + Status: 200, + }, + } + + assert.Empty(t, errs) + assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls) +} + // TestMultiBidder makes sure all the requests get sent, and the responses processed. // Because this is done in parallel, it should be run under the race detector. func TestMultiBidder(t *testing.T) { @@ -891,86 +932,160 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } } -// TestBadResponseLogging makes sure that openrtb_ext works properly on malformed HTTP requests. -func TestBadRequestLogging(t *testing.T) { - info := &httpCallInfo{ - err: errors.New("Bad request"), - } - ext := makeExt(info) - if ext.Uri != "" { - t.Errorf("The URI should be empty. Got %s", ext.Uri) - } - if ext.RequestBody != "" { - t.Errorf("The request body should be empty. Got %s", ext.RequestBody) - } - if ext.ResponseBody != "" { - t.Errorf("The response body should be empty. Got %s", ext.ResponseBody) - } - if ext.Status != 0 { - t.Errorf("The Status code should be 0. Got %d", ext.Status) - } - if len(ext.RequestHeaders) > 0 { - t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders) - } -} - -// TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response. -func TestBadResponseLogging(t *testing.T) { - info := &httpCallInfo{ - request: &adapters.RequestData{ - Uri: "test.com", - Body: []byte("request body"), - Headers: http.Header{ - "header-1": []string{"value-1"}, +func TestMakeExt(t *testing.T) { + testCases := []struct { + description string + given *httpCallInfo + expected *openrtb_ext.ExtHttpCall + }{ + { + description: "Nil", + given: nil, + expected: &openrtb_ext.ExtHttpCall{}, + }, + { + description: "Empty", + given: &httpCallInfo{ + err: nil, + response: nil, + request: nil, }, + expected: &openrtb_ext.ExtHttpCall{}, + }, + { + description: "Request & Response - No Error", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + ResponseBody: "responseBody", + Status: 999, + }, + }, + { + description: "Request & Response - Error", + given: &httpCallInfo{ + err: errors.New("error"), + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + }, + }, + { + description: "Request Only", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: nil, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + }, + }, { + description: "Response Only", + given: &httpCallInfo{ + err: nil, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{}, }, - err: errors.New("Bad response"), - } - ext := makeExt(info) - if ext.Uri != info.request.Uri { - t.Errorf("The URI should be test.com. Got %s", ext.Uri) - } - if ext.RequestBody != string(info.request.Body) { - t.Errorf("The request body should be empty. Got %s", ext.RequestBody) - } - if ext.ResponseBody != "" { - t.Errorf("The response body should be empty. Got %s", ext.ResponseBody) } - if ext.Status != 0 { - t.Errorf("The Status code should be 0. Got %d", ext.Status) + + for _, test := range testCases { + result := makeExt(test.given) + assert.Equal(t, test.expected, result, test.description) } - assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"") } -// TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful. -func TestSuccessfulResponseLogging(t *testing.T) { - info := &httpCallInfo{ - request: &adapters.RequestData{ - Uri: "test.com", - Body: []byte("request body"), - Headers: http.Header{ - "header-1": []string{"value-1", "value-2"}, - }, +func TestRemoveSensitiveHeaders(t *testing.T) { + testCases := []struct { + description string + given http.Header + expected http.Header + }{ + { + description: "Nil", + given: nil, + expected: nil, }, - response: &adapters.ResponseData{ - StatusCode: 200, - Body: []byte("response body"), + { + description: "Empty", + given: http.Header{}, + expected: map[string][]string{}, + }, + { + description: "One", + given: makeHeader(map[string][]string{"Key1": {"value1"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}}), + }, + { + description: "Many", + given: makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}), + }, + { + description: "Authorization Header Omitted", + given: makeHeader(map[string][]string{"authorization": {"secret"}}), + expected: http.Header{}, + }, + { + description: "Authorization Header Omitted - Case Insensitive", + given: makeHeader(map[string][]string{"AuThOrIzAtIoN": {"secret"}}), + expected: http.Header{}, + }, + { + description: "Authorization Header Omitted + Other Keys", + given: makeHeader(map[string][]string{"authorization": {"secret"}, "Key1": {"value1"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}}), }, } - ext := makeExt(info) - if ext.Uri != info.request.Uri { - t.Errorf("The URI should be test.com. Got %s", ext.Uri) - } - if ext.RequestBody != string(info.request.Body) { - t.Errorf("The request body should be \"request body\". Got %s", ext.RequestBody) - } - if ext.ResponseBody != string(info.response.Body) { - t.Errorf("The response body should be \"response body\". Got %s", ext.ResponseBody) + + for _, test := range testCases { + removeSensitiveHeaders(test.given) + assert.Equal(t, test.expected, test.given, test.description) } - if ext.Status != info.response.StatusCode { - t.Errorf("The Status code should be 0. Got %d", ext.Status) +} + +func makeHeader(v map[string][]string) http.Header { + h := http.Header{} + for key, values := range v { + for _, value := range values { + h.Add(key, value) + } } - assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders) + return h } func TestMobileNativeTypes(t *testing.T) { From 6fa5771c0e380f5c9864434f0cfae517361a9507 Mon Sep 17 00:00:00 2001 From: Pavel Dunyashev Date: Thu, 1 Apr 2021 23:57:11 +0300 Subject: [PATCH 367/603] New Adapter: Bidmachine (#1769) --- adapters/bidmachine/bidmachine.go | 219 ++++++++++++++++++ adapters/bidmachine/bidmachine_test.go | 28 +++ .../rewarded_interstitial_no_battr.json | 128 ++++++++++ .../rewarded_interstitial_w_battr.json | 128 ++++++++++ .../exemplary/rewarded_video_no_battr.json | 132 +++++++++++ .../exemplary/rewarded_video_w_battr.json | 132 +++++++++++ .../exemplary/simple_banner.json | 118 ++++++++++ .../exemplary/simple_interstitial.json | 120 ++++++++++ .../exemplary/simple_video.json | 124 ++++++++++ .../bidmachinetest/params/race/banner.json | 5 + .../bidmachinetest/params/race/video.json | 5 + .../supplemental/empty-banner-format .json | 19 ++ .../supplemental/missing-banner-format.json | 26 +++ .../supplemental/status-204.json | 66 ++++++ .../supplemental/status-400.json | 69 ++++++ .../supplemental/status-403.json | 69 ++++++ .../supplemental/status-408.json | 69 ++++++ .../supplemental/status-500.json | 69 ++++++ .../supplemental/status-502.json | 69 ++++++ .../supplemental/status-503.json | 69 ++++++ .../supplemental/status-504.json | 69 ++++++ .../supplemental/wrong-host.json | 35 +++ adapters/bidmachine/params_test.go | 78 +++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_bidmachine.go | 7 + static/bidder-info/bidmachine.yaml | 8 + static/bidder-params/bidmachine.json | 31 +++ usersync/usersyncers/syncer_test.go | 1 + 30 files changed, 1898 insertions(+) create mode 100644 adapters/bidmachine/bidmachine.go create mode 100644 adapters/bidmachine/bidmachine_test.go create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_video.json create mode 100644 adapters/bidmachine/bidmachinetest/params/race/banner.json create mode 100644 adapters/bidmachine/bidmachinetest/params/race/video.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-204.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-400.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-403.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-408.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-500.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-502.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-503.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-504.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json create mode 100644 adapters/bidmachine/params_test.go create mode 100644 openrtb_ext/imp_bidmachine.go create mode 100644 static/bidder-info/bidmachine.yaml create mode 100644 static/bidder-params/bidmachine.json diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go new file mode 100644 index 00000000000..3c9a5b679e2 --- /dev/null +++ b/adapters/bidmachine/bidmachine.go @@ -0,0 +1,219 @@ +package bidmachine + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strconv" + "text/template" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint template.Template +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + headers := http.Header{} + headers.Add("Content-Type", "application/json") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + impressions := request.Imp + result := make([]*adapters.RequestData, 0, len(impressions)) + errs := make([]error, 0, len(impressions)) + + for _, impression := range impressions { + if impression.Banner != nil { + banner := impression.Banner + if banner.W == nil && banner.H == nil { + if banner.Format == nil { + errs = append(errs, &errortypes.BadInput{ + Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format is missing. At least one is required", + }) + continue + } + if len(banner.Format) == 0 { + errs = append(errs, &errortypes.BadInput{ + Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format array is empty. At least one is required", + }) + continue + } + } + + } + + var bidderExt adapters.ExtImpBidder + err := json.Unmarshal(impression.Ext, &bidderExt) + if err != nil { + errs = append(errs, err) + continue + } + + var impressionExt openrtb_ext.ExtImpBidmachine + err = json.Unmarshal(bidderExt.Bidder, &impressionExt) + if err != nil { + errs = append(errs, err) + continue + } + url, err := a.buildEndpointURL(impressionExt) + if err != nil { + errs = append(errs, err) + continue + } + if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 { + if impression.Banner != nil && !hasRewardedBattr(impression.Banner.BAttr) { + bannerCopy := *impression.Banner + bannerCopy.BAttr = copyBAttrWithRewardedInventory(bannerCopy.BAttr) + impression.Banner = &bannerCopy + } + if impression.Video != nil && !hasRewardedBattr(impression.Video.BAttr) { + videoCopy := *impression.Video + videoCopy.BAttr = copyBAttrWithRewardedInventory(videoCopy.BAttr) + impression.Video = &videoCopy + } + } + request.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + result = append(result, &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: body, + Headers: headers, + }) + } + + request.Imp = impressions + + return result, errs +} + +func hasRewardedBattr(attr []openrtb2.CreativeAttribute) bool { + for i := 0; i < len(attr); i++ { + if attr[i] == openrtb2.CreativeAttribute(16) { + return true + } + } + return false +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + switch responseData.StatusCode { + case http.StatusNoContent: + return nil, nil + case http.StatusServiceUnavailable: + fallthrough + case http.StatusBadRequest: + fallthrough + case http.StatusUnauthorized: + fallthrough + case http.StatusForbidden: + return nil, []error{&errortypes.BadInput{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), + }} + case http.StatusOK: + break + default: + return nil, []error{&errortypes.BadServerResponse{ + Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), + }} + } + + var bidResponse openrtb2.BidResponse + err := json.Unmarshal(responseData.Body, &bidResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + thisBid := bid + bidType := GetMediaTypeForImp(bid.ImpID, request.Imp) + if bidType == UndefinedMediaType { + errs = append(errs, &errortypes.BadServerResponse{ + Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID, + }) + continue + } + response.Bids = append(response.Bids, &adapters.TypedBid{ + Bid: &thisBid, + BidType: bidType, + }) + } + } + + return response, errs +} + +// Builder builds a new instance of the Bidmachine adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: *template, + } + + return bidder, nil +} + +const UndefinedMediaType = openrtb_ext.BidType("") + +func (a *adapter) buildEndpointURL(params openrtb_ext.ExtImpBidmachine) (string, error) { + endpointParams := macros.EndpointTemplateParams{Host: params.Host} + uriString, errMacros := macros.ResolveMacros(a.endpoint, endpointParams) + if errMacros != nil { + return "", &errortypes.BadInput{ + Message: "Failed to resolve host macros", + } + } + uri, errUrl := url.Parse(uriString) + if errUrl != nil || uri.Scheme == "" || uri.Host == "" { + return "", &errortypes.BadInput{ + Message: "Failed to create final URL with provided host", + } + } + uri.Path = path.Join(uri.Path, params.Path) + uri.Path = path.Join(uri.Path, params.SellerID) + return uri.String(), nil +} + +func copyBAttrWithRewardedInventory(src []openrtb2.CreativeAttribute) []openrtb2.CreativeAttribute { + dst := make([]openrtb2.CreativeAttribute, len(src)) + copy(dst, src) + dst = append(dst, openrtb2.CreativeAttribute(16)) + return dst +} + +func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType + } + } + return UndefinedMediaType +} diff --git a/adapters/bidmachine/bidmachine_test.go b/adapters/bidmachine/bidmachine_test.go new file mode 100644 index 00000000000..9dc82150a82 --- /dev/null +++ b/adapters/bidmachine/bidmachine_test.go @@ -0,0 +1,28 @@ +package bidmachine + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{ + Endpoint: "https://{{.Host}}.bidmachine.io"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "bidmachinetest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json new file mode 100644 index 00000000000..96bf06be015 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1, 16], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json new file mode 100644 index 00000000000..08b187e5710 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1, 16], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1, 16], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json new file mode 100644 index 00000000000..72bf59d7d1e --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1, 16], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json new file mode 100644 index 00000000000..0d681e160a8 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1, 16], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1, 16], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "prebid": + { + "is_rewarded_inventory": 1 + }, + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json new file mode 100644 index 00000000000..bde25edc92a --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": + { + "battr": [1], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 50 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": + { + "battr": [1], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 50 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json new file mode 100644 index 00000000000..b49c85e54af --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "banner": + { + "battr": [1], + "mimes": [ + "image/jpeg", + "image/jpg", + "image/gif", + "image/png" + ], + "w": 320, + "h": 480 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json new file mode 100644 index 00000000000..e6e1d2dc70f --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1", + "headers": + { + "Content-Type": [ + "application/json" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": + { + "battr": [1], + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "protocols": [ + 1, + 2, + 3 + ], + "w": 320, + "h": 480 + }, + "ext": + { + "bidder": + { + "host": "api-us", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + }] + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }], + "seat": "bidmachine" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 100, + "adm": "test-adm", + "cid": "test-cid", + "crid": "test-crid" + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/params/race/banner.json b/adapters/bidmachine/bidmachinetest/params/race/banner.json new file mode 100644 index 00000000000..0402e4a129b --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "seller_id": "1", + "path": "auction/rtb/v2", + "host": "api-eu" +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/params/race/video.json b/adapters/bidmachine/bidmachinetest/params/race/video.json new file mode 100644 index 00000000000..0402e4a129b --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "seller_id": "1", + "path": "auction/rtb/v2", + "host": "api-eu" +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json new file mode 100644 index 00000000000..b454ce3d7ad --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format array is empty. At least one is required", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json new file mode 100644 index 00000000000..563737e1126 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + {}, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format is missing. At least one is required", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-204.json b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json new file mode 100644 index 00000000000..daa7f00a8e0 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 204, + "body": + {} + } + }], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-400.json b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json new file mode 100644 index 00000000000..5eb4ccfcebf --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 400, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 400 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-403.json b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json new file mode 100644 index 00000000000..3434275bf06 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 403, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 403 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-408.json b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json new file mode 100644 index 00000000000..51965461a81 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 408, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 408 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-500.json b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json new file mode 100644 index 00000000000..f7abfb0fdc4 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 500, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 500 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-502.json b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json new file mode 100644 index 00000000000..44dabb052a2 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 502, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 502 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-503.json b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json new file mode 100644 index 00000000000..979ac4f642a --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 503, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 503 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-504.json b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json new file mode 100644 index 00000000000..918ada2c150 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1", + "body": + { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": + { + "format": [ + { + "h": 250, + "w": 300 + }] + }, + "ext": + { + "bidder": + { + "seller_id": "1", + "host": "api-eu", + "path": "auction/rtb/v2" + } + } + }] + } + }, + "mockResponse": + { + "status": 504, + "body": "server text here" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "unexpected status code: 504 \"server text here\"", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json new file mode 100644 index 00000000000..82ee3269f46 --- /dev/null +++ b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-id-1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "host": "`", + "path": "auction/rtb/v2", + "seller_id": "1" + } + } + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to create final URL with provided host", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} + diff --git a/adapters/bidmachine/params_test.go b/adapters/bidmachine/params_test.go new file mode 100644 index 00000000000..15ebbcbf128 --- /dev/null +++ b/adapters/bidmachine/params_test.go @@ -0,0 +1,78 @@ +package bidmachine + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Bidmachine params: %s \n Error: %s", validParam, err) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"seller_id":"1", "host":"api-eu", "path":"auction/rtb/v2"}`, + `{"seller_id":"1", "host":"api-us", "path":"auction/rtb/v2"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"some_random_field":""}`, + `{"seller_id":""}`, + `{"seller_id": 1}`, + `{"seller_id": 1.2}`, + `{"seller_id": null}`, + `{"seller_id": true}`, + `{"seller_id": []}`, + `{"seller_id": {}}`, + `{"host":""}`, + `{"host": 1}`, + `{"host": 1.2}`, + `{"host": null}`, + `{"host": true}`, + `{"host": []}`, + `{"host": {}}`, + `{"path":""}`, + `{"path": 1}`, + `{"path": 1.2}`, + `{"path": null}`, + `{"path": true}`, + `{"path": []}`, + `{"path": {}}`, + `{"seller_id":"", "path": "", host: ""}`, + `{"seller_id": 1, "path": 2, host: 3}`, + `{"seller_id": 1.2}, "path": 5.5, host: 3.3`, + `{"seller_id": null, "path": null, host: null}`, + `{"seller_id": true, "path": false, host: true}`, + `{"seller_id": [], "path": [], host: []}`, + `{"seller_id": {}, "path": {}, host: {}}`, +} diff --git a/config/config.go b/config/config.go index bc15a947c7e..e22080fe602 100644 --- a/config/config.go +++ b/config/config.go @@ -829,6 +829,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}") + v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 46c967065cb..6c649fde5c8 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -29,6 +29,7 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/bidmachine" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/connectad" @@ -137,6 +138,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderBeachfront: beachfront.Builder, openrtb_ext.BidderBeintoo: beintoo.Builder, openrtb_ext.BidderBetween: between.Builder, + openrtb_ext.BidderBidmachine: bidmachine.Builder, openrtb_ext.BidderBrightroll: brightroll.Builder, openrtb_ext.BidderColossus: colossus.Builder, openrtb_ext.BidderConnectAd: connectad.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e03c234b257..66dcbebcaff 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -100,6 +100,7 @@ const ( BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" + BidderBidmachine BidderName = "bidmachine" BidderBrightroll BidderName = "brightroll" BidderColossus BidderName = "colossus" BidderConnectAd BidderName = "connectad" @@ -208,6 +209,7 @@ func CoreBidderNames() []BidderName { BidderBeachfront, BidderBeintoo, BidderBetween, + BidderBidmachine, BidderBrightroll, BidderColossus, BidderConnectAd, diff --git a/openrtb_ext/imp_bidmachine.go b/openrtb_ext/imp_bidmachine.go new file mode 100644 index 00000000000..20491d25aca --- /dev/null +++ b/openrtb_ext/imp_bidmachine.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpBidmachine struct { + Host string `json:"host"` + Path string `json:"path"` + SellerID string `json:"seller_id"` +} diff --git a/static/bidder-info/bidmachine.yaml b/static/bidder-info/bidmachine.yaml new file mode 100644 index 00000000000..6868125b6e6 --- /dev/null +++ b/static/bidder-info/bidmachine.yaml @@ -0,0 +1,8 @@ +maintainer: + email: hi@bidmachine.io +gvlVendorID: 736 +capabilities: + app: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-params/bidmachine.json b/static/bidder-params/bidmachine.json new file mode 100644 index 00000000000..f3971c00274 --- /dev/null +++ b/static/bidder-params/bidmachine.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bidmachine Adapter Params", + "description": "A schema which validates params accepted by the Kidoz adapter", + "type": "object", + "properties": { + "host": { + "$ref": "#/definitions/non-empty-string", + "description": "Host" + }, + "path": { + "$ref": "#/definitions/non-empty-string", + "description": "URL path" + }, + "seller_id": { + "$ref": "#/definitions/non-empty-string", + "description": "Seller Identifier" + } + }, + "definitions": { + "non-empty-string": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "host", + "path", + "seller_id" + ] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 5b2e80463ac..51ea19d7577 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -106,6 +106,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdot: true, openrtb_ext.BidderAdprime: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderBidmachine: true, openrtb_ext.BidderEpom: true, openrtb_ext.BidderDecenterAds: true, openrtb_ext.BidderInMobi: true, From 8193e30d8f8e6175b8cb4884bcdbc83b3cddcd20 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 1 Apr 2021 23:12:20 +0200 Subject: [PATCH 368/603] New Adapter: Criteo (#1775) --- adapters/criteo/criteo.go | 114 +++++ adapters/criteo/criteo_test.go | 28 ++ .../exemplary/simple-banner-cookie-uid.json | 115 +++++ .../exemplary/simple-banner-inapp.json | 110 +++++ .../exemplary/simple-banner-uid.json | 132 +++++ .../204-response-from-target.json | 81 ++++ .../400-response-from-target.json | 86 ++++ .../500-response-from-target.json | 86 ++++ ...ccpa-with-consent-simple-banner-inapp.json | 115 +++++ .../ccpa-with-consent-simple-banner-uid.json | 139 ++++++ ...a-without-consent-simple-banner-inapp.json | 85 ++++ ...cpa-without-consent-simple-banner-uid.json | 105 ++++ ...gdpr-with-consent-simple-banner-inapp.json | 126 +++++ .../gdpr-with-consent-simple-banner-uid.json | 142 ++++++ ...r-without-consent-simple-banner-inapp.json | 92 ++++ ...dpr-without-consent-simple-banner-uid.json | 108 +++++ .../multislots-simple-banner-inapp.json | 213 +++++++++ ...mple-banner-uid-different-network-ids.json | 84 ++++ .../multislots-simple-banner-uid.json | 233 +++++++++ ...-direct-size-and-formats-not-included.json | 132 +++++ ...e-banner-with-direct-size-and-formats.json | 132 +++++ .../simple-banner-with-direct-size.json | 126 +++++ .../supplemental/simple-banner-with-ipv6.json | 126 +++++ adapters/criteo/generators.go | 36 ++ adapters/criteo/models.go | 299 ++++++++++++ adapters/criteo/models_test.go | 452 ++++++++++++++++++ adapters/criteo/params_test.go | 63 +++ adapters/criteo/usersync.go | 14 + adapters/criteo/usersync_test.go | 26 + config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_criteo.go | 7 + static/bidder-info/criteo.yaml | 10 + static/bidder-params/criteo.json | 30 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 37 files changed, 3655 insertions(+) create mode 100644 adapters/criteo/criteo.go create mode 100644 adapters/criteo/criteo_test.go create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/204-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/400-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/500-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json create mode 100644 adapters/criteo/generators.go create mode 100644 adapters/criteo/models.go create mode 100644 adapters/criteo/models_test.go create mode 100644 adapters/criteo/params_test.go create mode 100644 adapters/criteo/usersync.go create mode 100644 adapters/criteo/usersync_test.go create mode 100644 openrtb_ext/imp_criteo.go create mode 100644 static/bidder-info/criteo.yaml create mode 100644 static/bidder-params/criteo.json diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go new file mode 100644 index 00000000000..f045c9f009b --- /dev/null +++ b/adapters/criteo/criteo.go @@ -0,0 +1,114 @@ +package criteo + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + uri string + slotIDGenerator slotIDGenerator +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + criteoRequest, errs := newCriteoRequest(a.slotIDGenerator, request) + if len(errs) > 0 { + return nil, errs + } + + jsonRequest, err := json.Marshal(criteoRequest) + if err != nil { + return nil, []error{err} + } + + rqData := adapters.RequestData{ + Method: "POST", + Uri: a.uri, + Body: jsonRequest, + Headers: getCriteoRequestHeaders(&criteoRequest), + } + + return []*adapters.RequestData{&rqData}, nil +} + +func getCriteoRequestHeaders(criteoRequest *criteoRequest) http.Header { + headers := http.Header{} + + // criteoRequest is known not to be nil + // If there was an error generating it from newCriteoRequest, the errors will be returned immediately + // and this method won't be called + + if criteoRequest.User.CookieID != "" { + headers.Add("Cookie", "uid="+criteoRequest.User.CookieID) + } + + if criteoRequest.User.IP != "" { + headers.Add("X-Forwarded-For", criteoRequest.User.IP) + } + + if criteoRequest.User.IPv6 != "" { + headers.Add("X-Forwarded-For", criteoRequest.User.IPv6) + } + + if criteoRequest.User.UA != "" { + headers.Add("User-Agent", criteoRequest.User.UA) + } + + return headers +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + bidResponse, err := newCriteoResponseFromBytes(response.Body) + if err != nil { + return nil, []error{err} + } + + bidderResponse := adapters.NewBidderResponse() + bidderResponse.Bids = make([]*adapters.TypedBid, len(bidResponse.Slots)) + + for i := 0; i < len(bidResponse.Slots); i++ { + bidderResponse.Bids[i] = &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + ID: bidResponse.Slots[i].ID, + ImpID: bidResponse.Slots[i].ImpID, + Price: bidResponse.Slots[i].CPM, + AdM: bidResponse.Slots[i].Creative, + W: bidResponse.Slots[i].Width, + H: bidResponse.Slots[i].Height, + CrID: bidResponse.Slots[i].CreativeID, + }, + BidType: openrtb_ext.BidTypeBanner, + } + } + + return bidderResponse, nil +} + +// Builder builds a new instance of the Criteo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + return builderWithGuidGenerator(bidderName, config, newRandomSlotIDGenerator()) +} + +func builderWithGuidGenerator(bidderName openrtb_ext.BidderName, config config.Adapter, slotIDGenerator slotIDGenerator) (adapters.Bidder, error) { + return &adapter{ + uri: config.Endpoint, + slotIDGenerator: slotIDGenerator, + }, nil +} diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go new file mode 100644 index 00000000000..0a700734538 --- /dev/null +++ b/adapters/criteo/criteo_test.go @@ -0,0 +1,28 @@ +package criteo + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + + // Setup: + bidder, buildErr := builderWithGuidGenerator( + openrtb_ext.BidderCriteo, + config.Adapter{ + Endpoint: "https://bidder.criteo.com/cdb?profileId=230", + }, + newFakeGuidGenerator("00000000-0000-0000-00000000"), + ) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + // Execute & Verify: + adapterstest.RunJSONBidderTest(t, "criteotest", bidder) +} diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json new file mode 100755 index 00000000000..cb152f47c41 --- /dev/null +++ b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "criteo-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"], + "Cookie": ["uid=criteo-user-id"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "cookieuid": "criteo-user-id", + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json new file mode 100755 index 00000000000..0ec1b7f87a5 --- /dev/null +++ b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json new file mode 100755 index 00000000000..2499790bf5a --- /dev/null +++ b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/204-response-from-target.json b/adapters/criteo/criteotest/supplemental/204-response-from-target.json new file mode 100755 index 00000000000..9ac14ff674d --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/204-response-from-target.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeBidsErrors": [] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/400-response-from-target.json b/adapters/criteo/criteotest/supplemental/400-response-from-target.json new file mode 100755 index 00000000000..ce3a4b628f3 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/400-response-from-target.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/500-response-from-target.json b/adapters/criteo/criteotest/supplemental/500-response-from-target.json new file mode 100755 index 00000000000..db6c16a38b7 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/500-response-from-target.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json new file mode 100755 index 00000000000..b047fa183d3 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json new file mode 100755 index 00000000000..f839624af1e --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json new file mode 100755 index 00000000000..77a37361141 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YNN" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YNN", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json new file mode 100755 index 00000000000..31c14fc4213 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "us_privacy": "1YNN" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YNN" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json new file mode 100755 index 00000000000..416053dbc67 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json new file mode 100755 index 00000000000..e8374616db3 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent", + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json new file mode 100755 index 00000000000..7385b93752f --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithnoconsent" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithnoconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json new file mode 100755 index 00000000000..dbc5fb32848 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithoutconsent", + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithoutconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json new file mode 100755 index 00000000000..75f4118eb93 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json @@ -0,0 +1,213 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ifa": "test-ifa-123456", + "os": "android" + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json new file mode 100755 index 00000000000..dcb51825058 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 123456 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 467890 + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Bid request has slots coming with several network IDs which is not allowed", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json new file mode 100755 index 00000000000..e5de75e04c0 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json @@ -0,0 +1,233 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json new file mode 100755 index 00000000000..a0cc53d00f3 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 250, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "250x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json new file mode 100755 index 00000000000..016c16a866f --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json new file mode 100755 index 00000000000..68e45da9fb3 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json new file mode 100755 index 00000000000..e248db9bc30 --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/generators.go b/adapters/criteo/generators.go new file mode 100644 index 00000000000..0ae0cecac78 --- /dev/null +++ b/adapters/criteo/generators.go @@ -0,0 +1,36 @@ +package criteo + +import "github.com/gofrs/uuid" + +type slotIDGenerator interface { + NewSlotID() (string, error) +} + +type randomSlotIDGenerator struct{} + +func newRandomSlotIDGenerator() randomSlotIDGenerator { + return randomSlotIDGenerator{} +} + +func (g randomSlotIDGenerator) NewSlotID() (string, error) { + guid, err := uuid.NewV4() + if err != nil { + return "", err + } + + return guid.String(), nil +} + +type fakeSlotIDGenerator struct { + fakeSlotID string +} + +func newFakeGuidGenerator(fakeSlotID string) fakeSlotIDGenerator { + return fakeSlotIDGenerator{ + fakeSlotID, + } +} + +func (f fakeSlotIDGenerator) NewSlotID() (string, error) { + return f.fakeSlotID, nil +} diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go new file mode 100644 index 00000000000..64f6081d781 --- /dev/null +++ b/adapters/criteo/models.go @@ -0,0 +1,299 @@ +package criteo + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type criteoRequest struct { + ID string `json:"id,omitempty"` + Publisher criteoPublisher `json:"publisher,omitempty"` + User criteoUser `json:"user,omitempty"` + GdprConsent criteoGdprConsent `json:"gdprconsent,omitempty"` + Slots []criteoRequestSlot `json:"slots,omitempty"` + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` +} + +func newCriteoRequest(slotIDGenerator slotIDGenerator, request *openrtb2.BidRequest) (criteoRequest, []error) { + var errs []error + + // request cannot be nil by design + + criteoRequest := criteoRequest{} + + criteoRequest.ID = request.ID + + // Extracting request slots + if len(request.Imp) > 0 { + criteoSlots, slotsErr := newCriteoRequestSlots(slotIDGenerator, request.Imp) + if len(slotsErr) > 0 { + return criteoRequest, slotsErr + } + criteoRequest.Slots = criteoSlots + } + + var networkId *int64 + for _, criteoSlot := range criteoRequest.Slots { + if networkId == nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID > 0 { + networkId = criteoSlot.NetworkID + } else if networkId != nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID != *networkId { + return criteoRequest, []error{&errortypes.BadInput{ + Message: "Bid request has slots coming with several network IDs which is not allowed", + }} + } + } + + criteoRequest.Publisher = newCriteoPublisher(networkId, request.App, request.Site) + + var regsExt *openrtb_ext.ExtRegs + if request.Regs != nil && request.Regs.Ext != nil { + if err := json.Unmarshal(request.Regs.Ext, ®sExt); err != nil { + errs = append(errs, err) + } + } + + criteoRequest.User = newCriteoUser(request.User, request.Device, regsExt) + + if gdprConsent, err := newCriteoGdprConsent(request.User, regsExt); err != nil { + errs = append(errs, err) + } else { + criteoRequest.GdprConsent = gdprConsent + } + + if request.User != nil && request.User.Ext != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + errs = append(errs, err) + } else { + criteoRequest.Eids = extUser.Eids + } + } + + return criteoRequest, errs +} + +type criteoPublisher struct { + SiteID string `json:"siteid,omitempty"` + BundleID string `json:"bundleid,omitempty"` + URL string `json:"url,omitempty"` + NetworkID *int64 `json:"networkid,omitempty"` +} + +func newCriteoPublisher(networkId *int64, app *openrtb2.App, site *openrtb2.Site) criteoPublisher { + // Both app and site cannot be nil at the same time by design in PBS + + criteoPublisher := criteoPublisher{} + + if networkId != nil && *networkId > 0 { + criteoPublisher.NetworkID = networkId + } + + if app != nil { + criteoPublisher.BundleID = app.Bundle + } + + if site != nil { + criteoPublisher.SiteID = site.ID + criteoPublisher.URL = site.Page + } + + return criteoPublisher +} + +type criteoUser struct { + DeviceID string `json:"deviceid,omitempty"` + DeviceOS string `json:"deviceos,omitempty"` + DeviceIDType string `json:"deviceidtype,omitempty"` + CookieID string `json:"cookieuid,omitempty"` + UID string `json:"uid,omitempty"` + IP string `json:"ip,omitempty"` + IPv6 string `json:"ipv6,omitempty"` + UA string `json:"ua,omitempty"` + UspIab string `json:"uspIab,omitempty"` +} + +func newCriteoUser(user *openrtb2.User, device *openrtb2.Device, regsExt *openrtb_ext.ExtRegs) criteoUser { + criteoUser := criteoUser{} + + if user == nil && device == nil { + return criteoUser + } + + if user != nil { + criteoUser.CookieID = user.BuyerUID + } + + if device != nil { + deviceType := getDeviceType(device.OS) + criteoUser.DeviceIDType = deviceType + + criteoUser.DeviceOS = device.OS + criteoUser.DeviceID = device.IFA + criteoUser.IP = device.IP + criteoUser.IPv6 = device.IPv6 + criteoUser.UA = device.UA + } + + if regsExt != nil { + criteoUser.UspIab = regsExt.USPrivacy // CCPA + } + + return criteoUser +} + +type criteoGdprConsent struct { + GdprApplies *bool `json:"gdprapplies,omitempty"` + ConsentData string `json:"consentdata,omitempty"` +} + +func newCriteoGdprConsent(user *openrtb2.User, regsExt *openrtb_ext.ExtRegs) (criteoGdprConsent, error) { + consent := criteoGdprConsent{} + + if user == nil && regsExt == nil { + return consent, nil + } + + if user != nil && user.Ext != nil { + var userExt *openrtb_ext.ExtUser + if err := json.Unmarshal(user.Ext, &userExt); err != nil { + return consent, err + } + consent.ConsentData = userExt.Consent + } + + if regsExt != nil { + if regsExt.GDPR != nil { + gdprApplies := bool((*regsExt.GDPR & 1) == 1) + consent.GdprApplies = &gdprApplies + } + } + + return consent, nil +} + +type criteoRequestSlot struct { + SlotID string `json:"slotid,omitempty"` + ImpID string `json:"impid,omitempty"` + ZoneID *int64 `json:"zoneid,omitempty"` + NetworkID *int64 `json:"networkid,omitempty"` + Sizes []criteoRequestSize `json:"sizes,omitempty"` +} + +func newCriteoRequestSlots(slotIDGenerator slotIDGenerator, impressions []openrtb2.Imp) ([]criteoRequestSlot, []error) { + var errs []error + + // `impressions` known not to be nil or empty by design, PBS checks it upstream. + + // Criteo slot should comes with any of (both are ok as well): + // - `zoneid` + // - `networkid`, `slotid`, `sizes` + // + // if not, criteo will reject the slot. + + var criteoSlots = make([]criteoRequestSlot, len(impressions)) + + for i := 0; i < len(impressions); i++ { + criteoSlots[i] = criteoRequestSlot{} + + criteoSlots[i].ImpID = impressions[i].ID + + // Generating a random slot ID + generatedSlotID, err := slotIDGenerator.NewSlotID() + if err != nil { + errs = append(errs, err) + continue + } + criteoSlots[i].SlotID = generatedSlotID + + if impressions[i].Banner != nil { + if impressions[i].Banner.Format != nil { + criteoSlots[i].Sizes = make([]criteoRequestSize, len(impressions[i].Banner.Format)) + for idx, format := range impressions[i].Banner.Format { + criteoSlots[i].Sizes[idx] = newCriteoRequestSize(format.W, format.H) + } + } else if impressions[i].Banner.W != nil && *impressions[i].Banner.W > 0 && impressions[i].Banner.H != nil && *impressions[i].Banner.H > 0 { + criteoSlots[i].Sizes = make([]criteoRequestSize, 1) + criteoSlots[i].Sizes[0] = newCriteoRequestSize(*impressions[i].Banner.W, *impressions[i].Banner.H) + } + } + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(impressions[i].Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + + if bidderExt.Bidder != nil { + var criteoExt openrtb_ext.ExtImpCriteo + if err := json.Unmarshal(bidderExt.Bidder, &criteoExt); err != nil { + errs = append(errs, err) + continue + } + if criteoExt.ZoneID > 0 { + criteoSlots[i].ZoneID = &criteoExt.ZoneID + } + if criteoExt.NetworkID > 0 { + criteoSlots[i].NetworkID = &criteoExt.NetworkID + } + } + } + + return criteoSlots, errs +} + +type criteoRequestSize = string + +func newCriteoRequestSize(width int64, height int64) criteoRequestSize { + return fmt.Sprintf("%dx%d", width, height) +} + +var deviceType = map[string]string{ + "ios": "idfa", + "android": "gaid", + "unknown": "unknown", +} + +func getDeviceType(os string) string { + if os != "" { + if dtype, ok := deviceType[strings.ToLower(os)]; ok { + return dtype + } + } + + return deviceType["unknown"] +} + +type criteoResponse struct { + ID string `json:"id,omitempty"` + Slots []criteoResponseSlot `json:"slots,omitempty"` +} + +func newCriteoResponseFromBytes(bytes []byte) (criteoResponse, error) { + var err error + var bidResponse criteoResponse + + if err = json.Unmarshal(bytes, &bidResponse); err != nil { + return bidResponse, err + } + + return bidResponse, nil +} + +type criteoResponseSlot struct { + ID string `json:"id,omitempty"` + ImpID string `json:"impid,omitempty"` + ZoneID int64 `json:"zoneid,omitempty"` + NetworkID int64 `json:"networkid,omitempty"` + CPM float64 `json:"cpm,omitempty"` + Currency string `json:"currency,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` + Creative string `json:"creative,omitempty"` + CreativeID string `json:"creativeid,omitempty"` +} diff --git a/adapters/criteo/models_test.go b/adapters/criteo/models_test.go new file mode 100644 index 00000000000..9da7bda210b --- /dev/null +++ b/adapters/criteo/models_test.go @@ -0,0 +1,452 @@ +package criteo + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestGetDeviceType(t *testing.T) { + + // Setup: + deviceTypeCases := []struct { + deviceType string + expected string + }{ + {"ios", "idfa"}, + {"Ios", "idfa"}, + {"IOS", "idfa"}, + {"android", "gaid"}, + {"unknown", "unknown"}, + {"", "unknown"}, + {"qwerty", "unknown"}, + {"qWerty", "unknown"}, + {"abc", "unknown"}, + } + + for _, uc := range deviceTypeCases { + // Execute: + result := getDeviceType(uc.deviceType) + + // Verify: + if uc.expected != result { + t.Errorf("Bad getDeviceType for '%s'. Expected: %s, got %s", uc.deviceType, uc.expected, result) + } + } +} + +func TestNewCriteoRequest(t *testing.T) { + // Setup: + var ( + dummyRequestID = "random request ID" + dummyPublisherBundleID = "bundleid" + dummyPublisherURL = "test.com" + dummyPublisherSiteID = "siteid" + dummyUserDeviceID = "random-device-id" + dummyUserDeviceOS = "android" + dummyUserDeviceIDType = "gaid" + dummyUserCookieID = "random-cookie-id" + dummyUserIP = "1.1.1.1" + dummyUserUA = "random UA" + dummyGdprApplies = true + dummyGdprAppliesUint = int8(1) + dummyGdprConsentData = "randomconsentdata" + dummySlotID = "11111111-1111-1111-11111111" + dummySlotImpID = "fake-imp-id-1" + dummySlotZoneID = int64(1) + ) + + fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID) + + // The request doesn't make any sense but aims to fill every single criteo request fields + expectedCriteoRequest := criteoRequest{ + ID: dummyRequestID, + Publisher: criteoPublisher{ + SiteID: dummyPublisherSiteID, + BundleID: dummyPublisherBundleID, + URL: dummyPublisherURL, + }, + User: criteoUser{ + DeviceID: dummyUserDeviceID, + DeviceOS: dummyUserDeviceOS, + DeviceIDType: dummyUserDeviceIDType, + CookieID: dummyUserCookieID, + IP: dummyUserIP, + UA: dummyUserUA, + }, + GdprConsent: criteoGdprConsent{ + GdprApplies: &dummyGdprApplies, + ConsentData: dummyGdprConsentData, + }, + Slots: []criteoRequestSlot{ + { + SlotID: dummySlotID, + ImpID: dummySlotImpID, + ZoneID: &dummySlotZoneID, + }, + }, + } + + userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{ + Consent: dummyGdprConsentData, + }) + regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{ + GDPR: &dummyGdprAppliesUint, + }) + bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{ + ZoneID: dummySlotZoneID, + }) + impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{ + Bidder: bidderExtJSON, + }) + incomingRequest := &openrtb2.BidRequest{ + ID: dummyRequestID, + App: &openrtb2.App{ + Bundle: dummyPublisherBundleID, + }, + Site: &openrtb2.Site{ + ID: dummyPublisherSiteID, + Page: dummyPublisherURL, + }, + User: &openrtb2.User{ + BuyerUID: dummyUserCookieID, + Ext: userExtJSON, + }, + Regs: &openrtb2.Regs{ + Ext: regsExtJSON, + }, + Device: &openrtb2.Device{ + IFA: dummyUserDeviceID, + OS: dummyUserDeviceOS, + IP: dummyUserIP, + UA: dummyUserUA, + }, + Imp: []openrtb2.Imp{ + { + ID: dummySlotImpID, + Ext: impExtJSON, + }, + }, + } + + // Execute: + result, err := newCriteoRequest(fakeSlotIDGenerator, incomingRequest) + + // Verify: + if err != nil { + t.Errorf("newCriteoRequest has errors: %s", err) + } + + if expectedCriteoRequest.ID != result.ID || + !reflect.DeepEqual(expectedCriteoRequest, result) || + !reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) || + !reflect.DeepEqual(expectedCriteoRequest.User, result.User) || + !reflect.DeepEqual(expectedCriteoRequest.GdprConsent, result.GdprConsent) || + len(expectedCriteoRequest.Slots) != len(result.Slots) || + !reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestGetGdprConsent(t *testing.T) { + // Setup: + var ( + dummyGdprApplies = true + dummyGdprConsentData = "randomconsentdata" + dummyGdprAppliesUint = int8(1) + ) + + expectedCriteoRequest := criteoRequest{ + GdprConsent: criteoGdprConsent{ + GdprApplies: &dummyGdprApplies, + ConsentData: dummyGdprConsentData, + }, + } + + userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{ + Consent: dummyGdprConsentData, + }) + regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{ + GDPR: &dummyGdprAppliesUint, + }) + incomingRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: userExtJSON, + }, + Regs: &openrtb2.Regs{ + Ext: regsExtJSON, + }, + } + + var regsExt *openrtb_ext.ExtRegs + if incomingRequest.Regs != nil { + json.Unmarshal(incomingRequest.Regs.Ext, ®sExt) + } + + // Execute: + gdprConsent, _ := newCriteoGdprConsent(incomingRequest.User, regsExt) + result := criteoRequest{ + GdprConsent: gdprConsent, + } + + // Verify: + if !reflect.DeepEqual(expectedCriteoRequest, result) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("getGdprConsent was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestGetUser(t *testing.T) { + // Setup: + var ( + dummyUserDeviceID = "random-device-id" + dummyUserDeviceOS = "android" + dummyUserDeviceIDType = "gaid" + dummyUserCookieID = "random-cookie-id" + dummyUserIP = "1.1.1.1" + dummyUserUA = "random UA" + dummyCcpaString = "1YYY" + ) + expectedCriteoRequest := &criteoRequest{ + User: criteoUser{ + DeviceID: dummyUserDeviceID, + DeviceOS: dummyUserDeviceOS, + DeviceIDType: dummyUserDeviceIDType, + CookieID: dummyUserCookieID, + IP: dummyUserIP, + UA: dummyUserUA, + UspIab: dummyCcpaString, + }, + } + + regsExt := &openrtb_ext.ExtRegs{ + USPrivacy: dummyCcpaString, + } + regsExtData, err := json.Marshal(regsExt) + if err != nil { + t.Errorf("cannot marshal regsExt data") + } + + incomingRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{ + BuyerUID: dummyUserCookieID, + }, + Device: &openrtb2.Device{ + IFA: dummyUserDeviceID, + OS: dummyUserDeviceOS, + IP: dummyUserIP, + UA: dummyUserUA, + }, + Regs: &openrtb2.Regs{ + Ext: regsExtData, + }, + } + + // Execute: + result := &criteoRequest{ + User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt), + } + + // Verify: + if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestGetUser_NilUserAndDevice(t *testing.T) { + // Setup: + var dummyCcpaString = "1YYY" + + expectedCriteoRequest := &criteoRequest{} + + regsExt := &openrtb_ext.ExtRegs{ + USPrivacy: dummyCcpaString, + } + regsExtData, err := json.Marshal(regsExt) + if err != nil { + t.Errorf("cannot marshal regsExt data") + } + + incomingRequest := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: regsExtData, + }, + } + + // Execute: + result := &criteoRequest{ + User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt), + } + + // Verify: + if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestPublisher(t *testing.T) { + // Setup: + var ( + dummyPublisherSiteID = "siteid" + dummyPublisherBundleID = "bundleid" + dummyPublisherURL = "test.com" + dummyNetworkID int64 = 1234567 + ) + expectedCriteoRequest := &criteoRequest{ + Publisher: criteoPublisher{ + SiteID: dummyPublisherSiteID, + BundleID: dummyPublisherBundleID, + URL: dummyPublisherURL, + NetworkID: &dummyNetworkID, + }, + } + + incomingRequest := &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: dummyPublisherBundleID, + }, + Site: &openrtb2.Site{ + ID: dummyPublisherSiteID, + Page: dummyPublisherURL, + }, + } + + // Execute: + result := &criteoRequest{ + Publisher: newCriteoPublisher(&dummyNetworkID, incomingRequest.App, incomingRequest.Site), + } + + // Verify: + if !reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("getPublisher was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestGetRequestSlots(t *testing.T) { + // Setup: + var ( + dummySlotImpID = "fake-imp-id-1" + dummySlotZoneID = int64(1) + dummySlotID = "22222222-2222-2222-22222222" + ) + + fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID) + + expectedCriteoRequest := &criteoRequest{ + Slots: []criteoRequestSlot{ + { + SlotID: dummySlotID, + ImpID: dummySlotImpID, + ZoneID: &dummySlotZoneID, + }, + }, + } + + bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{ + ZoneID: dummySlotZoneID, + }) + impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{ + Bidder: bidderExtJSON, + }) + incomingRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: dummySlotImpID, + Ext: impExtJSON, + }, + }, + } + + // Execute: + slots, err := newCriteoRequestSlots(fakeSlotIDGenerator, incomingRequest.Imp) + result := &criteoRequest{ + Slots: slots, + } + + // Verify: + if err != nil { + t.Errorf("newCriteoRequestSlots has errors: %s", err) + } + + if len(expectedCriteoRequest.Slots) != len(result.Slots) || + !reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} + +func TestGetRequestMultipleSlots(t *testing.T) { + // Setup: + dummySlots := []struct { + ID string + ZoneID int64 + }{ + {"fake-imp-id-1", 1}, + {"fake-imp-id-2", 2}, + {"fake-imp-id-3", 3}, + {"fake-imp-id-4", 4}, + {"fake-imp-id-5", 5}, + } + + incomingRequest := &openrtb2.BidRequest{ + Imp: make([]openrtb2.Imp, len(dummySlots)), + } + slots := make([]criteoRequestSlot, len(dummySlots)) + + for i := range dummySlots { + // Build expected slots + slots[i] = criteoRequestSlot{ + ImpID: dummySlots[i].ID, + ZoneID: &dummySlots[i].ZoneID, + } + + // Build incoming request imps + bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{ + ZoneID: dummySlots[i].ZoneID, + }) + impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{ + Bidder: bidderExtJSON, + }) + incomingRequest.Imp[i] = openrtb2.Imp{ + ID: dummySlots[i].ID, + Ext: impExtJSON, + } + } + + expectedCriteoRequestSlots, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp) + expectedCriteoRequest := &criteoRequest{ + Slots: expectedCriteoRequestSlots, + } + + // Execute: + slotsResult, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp) + result := &criteoRequest{ + Slots: slotsResult, + } + + // Verify: + if err != nil { + t.Errorf("newCriteoRequestSlots has errors: %s", err) + } + + if len(expectedCriteoRequest.Slots) != len(result.Slots) || + !reflect.DeepEqual(expectedCriteoRequest.Slots, result.Slots) { + actualResultJSON, _ := json.Marshal(result) + expectedResultJSON, _ := json.Marshal(expectedCriteoRequest) + t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON) + } +} diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go new file mode 100644 index 00000000000..73ace617b2d --- /dev/null +++ b/adapters/criteo/params_test.go @@ -0,0 +1,63 @@ +package criteo + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/criteo.json +// +// These also validate the format of the external API: request.imp[i].ext.criteo + +// TestValidParams makes sure that the criteo schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderCriteo, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected criteo params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the criteo schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderCriteo, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneid": 123456}`, + `{"networkid": 78910}`, + `{"zoneid": 123456, "networkid": 78910}`, + `{"zoneid": 0, "networkid": 0}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"zoneid": -123}`, + `{"networkid": -321}`, + `{"zoneid": -123, "networkid": -321}`, + `{"zoneid": -1}`, + `{"networkid": -1}`, + `{"zoneid": -1, "networkid": -1}`, +} diff --git a/adapters/criteo/usersync.go b/adapters/criteo/usersync.go new file mode 100644 index 00000000000..3deba085ac4 --- /dev/null +++ b/adapters/criteo/usersync.go @@ -0,0 +1,14 @@ +package criteo + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +// NewCriteoSyncer user syncer for Criteo +// Criteo doesn't need user synchronization yet. +func NewCriteoSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("criteo", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/criteo/usersync_test.go b/adapters/criteo/usersync_test.go new file mode 100644 index 00000000000..9010d8bb450 --- /dev/null +++ b/adapters/criteo/usersync_test.go @@ -0,0 +1,26 @@ +package criteo + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestCriteoSyncer(t *testing.T) { + url := "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + temp := template.Must(template.New("sync-template").Parse(url)) + syncer := NewCriteoSyncer(temp) + info, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + }, + }) + assert.NoError(t, err) + assert.EqualValues(t, adapters.SyncTypeRedirect, info.Type) + assert.Equal(t, false, info.SupportCORS) + assert.Equal(t, "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D1%26gdpr_consent%3D%26uid%3D%24UID", info.URL) +} diff --git a/config/config.go b/config/config.go index e22080fe602..00aceba98cd 100644 --- a/config/config.go +++ b/config/config.go @@ -836,6 +836,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") + v.SetDefault("adapters.criteo.endpoint", "https://bidder.criteo.com/cdb?profileId=230") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb") v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 6c649fde5c8..0ea28f8a610 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -36,6 +36,7 @@ import ( "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" + "github.com/prebid/prebid-server/adapters/criteo" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" @@ -145,6 +146,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderConsumable: consumable.Builder, openrtb_ext.BidderConversant: conversant.Builder, openrtb_ext.BidderCpmstar: cpmstar.Builder, + openrtb_ext.BidderCriteo: criteo.Builder, openrtb_ext.BidderDatablocks: datablocks.Builder, openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 66dcbebcaff..4d95babc988 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -107,6 +107,7 @@ const ( BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" BidderCpmstar BidderName = "cpmstar" + BidderCriteo BidderName = "criteo" BidderDatablocks BidderName = "datablocks" BidderDmx BidderName = "dmx" BidderDecenterAds BidderName = "decenterads" @@ -216,6 +217,7 @@ func CoreBidderNames() []BidderName { BidderConsumable, BidderConversant, BidderCpmstar, + BidderCriteo, BidderDatablocks, BidderDecenterAds, BidderDeepintent, diff --git a/openrtb_ext/imp_criteo.go b/openrtb_ext/imp_criteo.go new file mode 100644 index 00000000000..e200aace496 --- /dev/null +++ b/openrtb_ext/imp_criteo.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.criteo +type ExtImpCriteo struct { + ZoneID int64 `json:"zoneId"` + NetworkID int64 `json:"networkId"` +} diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml new file mode 100644 index 00000000000..b27e1fae369 --- /dev/null +++ b/static/bidder-info/criteo.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "pi-direct@criteo.com" +gvlVendorID: 91 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json new file mode 100644 index 00000000000..9d348a7eded --- /dev/null +++ b/static/bidder-params/criteo.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Criteo adapter params", + "description": "The schema to validate Criteo specific params accepted by Criteo adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "number", + "description": "Impression's zone ID.", + "minimum": 0 + }, + "networkid": { + "type": "number", + "description": "Impression's network ID.", + "minimum": 0 + } + }, + "anyOf": [ + { + "required": [ + "zoneid" + ] + }, + { + "required": [ + "networkid" + ] + } + ] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7467eb261fa..f01e4e4b917 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -32,6 +32,7 @@ import ( "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/cpmstar" + "github.com/prebid/prebid-server/adapters/criteo" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" @@ -122,6 +123,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderCriteo, criteo.NewCriteoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 51ea19d7577..a389a007306 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -41,6 +41,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderConsumable): syncConfig, string(openrtb_ext.BidderConversant): syncConfig, string(openrtb_ext.BidderCpmstar): syncConfig, + string(openrtb_ext.BidderCriteo): syncConfig, string(openrtb_ext.BidderDatablocks): syncConfig, string(openrtb_ext.BidderDmx): syncConfig, string(openrtb_ext.BidderDeepintent): syncConfig, From 7cf60db6c0b60c50d7336c8a0befa1d9a41382e7 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 6 Apr 2021 10:56:38 -0400 Subject: [PATCH 369/603] Fix shared memory issue when stripping authorization header from bid requests (#1790) --- exchange/bidder.go | 13 ++++------- exchange/bidder_test.go | 52 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 5b6da499093..9707ddcb4fe 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -167,10 +167,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { if accountDebugAllowed { if bidder.config.DebugInfo.Allow { - // it's safe to mutate the request headers since from this point on the - // information is only used for debugging. - removeSensitiveHeaders(httpInfo.request.Headers) - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) } else { debugDisabledWarning := errortypes.Warning{ @@ -331,9 +327,10 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset var authorizationHeader = http.CanonicalHeaderKey("authorization") -// removeSensitiveHeaders mutates the http header object to remove sensitive information. -func removeSensitiveHeaders(h http.Header) { - h.Del(authorizationHeader) +func filterHeader(h http.Header) http.Header { + clone := h.Clone() + clone.Del(authorizationHeader) + return clone } // makeExt transforms information about the HTTP call into the contract class for the PBS response. @@ -343,7 +340,7 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { if httpInfo != nil && httpInfo.request != nil { ext.Uri = httpInfo.request.Uri ext.RequestBody = string(httpInfo.request.Body) - ext.RequestHeaders = httpInfo.request.Headers + ext.RequestHeaders = filterHeader(httpInfo.request.Headers) if httpInfo.err == nil && httpInfo.response != nil { ext.ResponseBody = string(httpInfo.response.Body) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 7c01cd84c83..41ca420a433 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -974,6 +974,50 @@ func TestMakeExt(t *testing.T) { Status: 999, }, }, + { + description: "Request & Response - No Error with Authorization removal", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}, "Authorization": {"secret"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + ResponseBody: "responseBody", + Status: 999, + }, + }, + { + description: "Request & Response - No Error with nil header", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: nil, + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: nil, + ResponseBody: "responseBody", + Status: 999, + }, + }, { description: "Request & Response - Error", given: &httpCallInfo{ @@ -1029,7 +1073,7 @@ func TestMakeExt(t *testing.T) { } } -func TestRemoveSensitiveHeaders(t *testing.T) { +func TestFilterHeader(t *testing.T) { testCases := []struct { description string given http.Header @@ -1043,7 +1087,7 @@ func TestRemoveSensitiveHeaders(t *testing.T) { { description: "Empty", given: http.Header{}, - expected: map[string][]string{}, + expected: http.Header{}, }, { description: "One", @@ -1073,8 +1117,8 @@ func TestRemoveSensitiveHeaders(t *testing.T) { } for _, test := range testCases { - removeSensitiveHeaders(test.given) - assert.Equal(t, test.expected, test.given, test.description) + result := filterHeader(test.given) + assert.Equal(t, test.expected, result, test.description) } } From 245accfc87dabe51f88b1b373c8332761e8a0ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Iwa=C5=84czak?= <36727380+piwanczak@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:47:12 +0200 Subject: [PATCH 370/603] RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak --- static/bidder-params/rtbhouse.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/static/bidder-params/rtbhouse.json b/static/bidder-params/rtbhouse.json index e8f6bd03cff..00732bedd2f 100644 --- a/static/bidder-params/rtbhouse.json +++ b/static/bidder-params/rtbhouse.json @@ -1,8 +1,13 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "RTB House Adapter Params", - "description": "A schema which validates params accepted by the RTB House adapter", - "type": "object", - "properties": {}, - "required": [] + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RTB House Adapter Params", + "description": "A schema which validates params accepted by the RTB House adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "The publisher’s ID provided by RTB House" + } + }, + "required": ["publisherId"] } From 570894372767189391eb67eb03c972680587032b Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 8 Apr 2021 11:34:27 -0700 Subject: [PATCH 371/603] Generate seatbid[].bid[].ext.prebid.bidid (#1772) --- config/config.go | 3 + config/config_test.go | 3 + exchange/bidder.go | 2 + exchange/events.go | 14 +- exchange/events_test.go | 42 +++-- exchange/exchange.go | 33 ++++ exchange/exchange_test.go | 105 +++++++----- exchange/exchangetest/bid-id-invalid.json | 161 ++++++++++++++++++ exchange/exchangetest/bid-id-valid.json | 154 +++++++++++++++++ .../events-vast-account-off-request-on.json | 11 +- exchange/targeting_test.go | 1 + openrtb_ext/bid.go | 1 + 12 files changed, 472 insertions(+), 58 deletions(-) create mode 100644 exchange/exchangetest/bid-id-invalid.json create mode 100644 exchange/exchangetest/bid-id-valid.json diff --git a/config/config.go b/config/config.go index 00aceba98cd..7b78fb26bde 100644 --- a/config/config.go +++ b/config/config.go @@ -80,6 +80,8 @@ type Configuration struct { RequestValidation RequestValidation `mapstructure:"request_validation"` // When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` + //When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead + GenerateBidID bool `mapstructure:"generate_bid_id"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -958,6 +960,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("account_defaults.debug_allow", true) v.SetDefault("certificates_file", "") v.SetDefault("auto_gen_source_tid", true) + v.SetDefault("generate_bid_id", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index 7ff5f0fafa1..43ee8fa21df 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -139,6 +139,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) 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) } var fullConfig = []byte(` @@ -230,6 +231,7 @@ certificates_file: /etc/ssl/cert.pem request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] +generate_bid_id: true `) var adapterExtraInfoConfig = []byte(` @@ -415,6 +417,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16") + cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/exchange/bidder.go b/exchange/bidder.go index 9707ddcb4fe..23b42d2a9d7 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -61,6 +61,7 @@ type adaptedBidder interface { // pbsOrtbBid.bidEvents is set by exchange when event tracking is enabled // pbsOrtbBid.dealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. // pbsOrtbBid.dealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false +// pbsOrtbBid.generatedBidID is unique bid id generated by prebid server if generate bid id option is enabled in config type pbsOrtbBid struct { bid *openrtb2.Bid bidType openrtb_ext.BidType @@ -69,6 +70,7 @@ type pbsOrtbBid struct { bidEvents *openrtb_ext.ExtBidPrebidEvents dealPriority int dealTierSatisfied bool + generatedBidID string } // pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder. diff --git a/exchange/events.go b/exchange/events.go index f15579c6d25..bedafddf5a0 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -4,7 +4,7 @@ import ( "encoding/json" "time" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/endpoints/events" @@ -62,7 +62,11 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex return } vastXML := makeVAST(bid) - if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { + bidID := bid.ID + if len(pbsBid.generatedBidID) > 0 { + bidID = pbsBid.generatedBidID + } + if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { bid.AdM = newVastXML } } @@ -103,10 +107,14 @@ func (ev *eventTracking) makeBidExtEvents(pbsBid *pbsOrtbBid, bidderName openrtb // makeEventURL returns an analytics event url for the requested type (win or imp) func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) string { + bidId := pbsBid.bid.ID + if len(pbsBid.generatedBidID) > 0 { + bidId = pbsBid.generatedBidID + } return events.EventRequestToUrl(ev.externalURL, &analytics.EventRequest{ Type: evType, - BidID: pbsBid.bid.ID, + BidID: bidId, Bidder: string(bidderName), AccountID: ev.accountID, Timestamp: ev.auctionTimestampMs, diff --git a/exchange/events_test.go b/exchange/events_test.go index 6685918ee6e..bde2b235987 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -13,6 +13,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { enabledForAccount bool enabledForRequest bool bidType openrtb_ext.BidType + generatedBidId string } tests := []struct { name string @@ -21,7 +22,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }{ { name: "banner: events enabled for request, disabled for account", - args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: &openrtb_ext.ExtBidPrebidEvents{ Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890", Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890", @@ -29,7 +30,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }, { name: "banner: events enabled for account, disabled for request", - args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: &openrtb_ext.ExtBidPrebidEvents{ Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890", Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890", @@ -37,19 +38,27 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }, { name: "banner: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: nil, }, { name: "video: events enabled for account and request", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, want: nil, }, { name: "video: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, want: nil, }, + { + name: "banner: use generated bid id", + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomId"}, + want: &openrtb_ext.ExtBidPrebidEvents{ + Win: "http://localhost/event?t=win&b=randomId&a=123456&bidder=openx&ts=1234567890", + Imp: "http://localhost/event?t=imp&b=randomId&a=123456&bidder=openx&ts=1234567890", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -60,7 +69,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId} assert.Equal(t, tt.want, evData.makeBidExtEvents(bid, openrtb_ext.BidderOpenx)) }) } @@ -71,6 +80,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { enabledForAccount bool enabledForRequest bool bidType openrtb_ext.BidType + generatedBidId string } tests := []struct { name string @@ -80,40 +90,46 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { }{ { name: "banner: events enabled for request, disabled for account", - args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`), }, { name: "banner: events enabled for account, disabled for request", - args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`), }, { name: "banner: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "video: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "video: events enabled for account and request", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "banner: broken json expected to fail patching", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`broken json`), want: nil, }, + { + name: "banner: generate bid id enabled", + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomID"}, + jsonBytes: []byte(`{"ID": "something"}`), + want: []byte(`{"ID": "something", "wurl":"http://localhost/event?t=win&b=randomID&a=123456&bidder=openx&ts=1234567890"}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -124,7 +140,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId} modifiedJSON, err := evData.modifyBidJSON(bid, openrtb_ext.BidderOpenx, tt.jsonBytes) if tt.want != nil { assert.NoError(t, err, "Unexpected error") diff --git a/exchange/exchange.go b/exchange/exchange.go index e06f87bf677..8dd6a06b480 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -17,6 +17,7 @@ import ( "github.com/mxmCherry/openrtb/v14/openrtb2" "github.com/prebid/prebid-server/stored_requests" + "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -61,6 +62,7 @@ type exchange struct { UsersyncIfAmbiguous bool privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher + bidIDGenerator BidIDGenerator } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -79,6 +81,24 @@ type bidResponseWrapper struct { bidder openrtb_ext.BidderName } +type BidIDGenerator interface { + New() (string, error) + Enabled() bool +} + +type bidIDGenerator struct { + enabled bool +} + +func (big *bidIDGenerator) Enabled() bool { + return big.enabled +} + +func (big *bidIDGenerator) New() (string, error) { + rawUuid, err := uuid.NewV4() + return rawUuid.String(), err +} + func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { return &exchange{ adapterMap: adapters, @@ -96,6 +116,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid GDPR: cfg.GDPR, LMT: cfg.LMT, }, + bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, } } @@ -192,6 +213,17 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + if e.bidIDGenerator.Enabled() { + for _, seatBid := range adapterBids { + for _, pbsBid := range seatBid.bids { + pbsBid.generatedBidID, err = e.bidIDGenerator.New() + if err != nil { + errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid")) + } + } + } + } + evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL) adapterBids = evTracking.modifyBidsForEvents(adapterBids) @@ -852,6 +884,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool Targeting: bid.bidTargets, Type: bid.bidType, Video: bid.bidVideo, + BidId: bid.generatedBidID, } if cacheInfo, found := e.getBidCacheInfo(bid, auc); found { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 1cffa79b127..beaa823d320 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -652,6 +652,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher + e.bidIDGenerator = &mockBidIDGenerator{false, false} // Define mock incoming bid requeset mockBidRequest := &openrtb2.BidRequest{ @@ -910,9 +911,10 @@ func TestBidReturnsCreative(t *testing.T) { // Test set up sampleBids := []*pbsOrtbBid{ { - bid: sampleOpenrtbBid, - bidType: openrtb_ext.BidTypeBanner, - bidTargets: map[string]string{}, + bid: sampleOpenrtbBid, + bidType: openrtb_ext.BidTypeBanner, + bidTargets: map[string]string{}, + generatedBidID: "randomId", }, } sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}} @@ -1625,8 +1627,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { EEACountriesMap: eeac, }, } - - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig) + bidIdGenerator := &mockBidIDGenerator{} + if spec.BidIDGenerator != nil { + *bidIdGenerator = *spec.BidIDGenerator + } + ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) debugLog := &DebugLog{} if spec.DebugLog != nil { @@ -1722,7 +1727,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy) Exchange { +func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator) Exchange { bidderAdapters := make(map[openrtb_ext.BidderName]adaptedBidder, len(expectations)) bidderInfos := make(config.BidderInfos, len(expectations)) for _, bidderName := range openrtb_ext.CoreBidderNames() { @@ -1772,7 +1777,27 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, + } +} + +type mockBidIDGenerator struct { + GenerateBidID bool `json:"generateBidID"` + ReturnError bool `json:"returnError"` +} + +func (big *mockBidIDGenerator) Enabled() bool { + return big.GenerateBidID +} + +func (big *mockBidIDGenerator) New() (string, error) { + + if big.ReturnError { + err := errors.New("Test error generating bid.ext.prebid.bidid") + return "", err } + return "mock_uuid", nil + } func newExtRequest() openrtb_ext.ExtRequest { @@ -1857,10 +1882,10 @@ func TestCategoryMapping(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1912,10 +1937,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1966,9 +1991,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2048,9 +2073,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2100,11 +2125,11 @@ func TestCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2179,11 +2204,11 @@ func TestNoCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2259,8 +2284,8 @@ func TestCategoryMappingBidderName(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2313,8 +2338,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2425,8 +2450,7 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*pbsOrtbBid{} for _, bid := range test.bids { currentBid := pbsOrtbBid{ - bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, - } + bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""} innerBids = append(innerBids, ¤tBid) } @@ -2474,8 +2498,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBidsApn1 := []*pbsOrtbBid{ &bid1_Apn1, @@ -2602,7 +2626,7 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2769,7 +2793,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2792,6 +2816,7 @@ type exchangeSpec struct { DebugLog *DebugLog `json:"debuglog,omitempty"` EventsEnabled bool `json:"events_enabled,omitempty"` StartTime int64 `json:"start_time_ms,omitempty"` + BidIDGenerator *mockBidIDGenerator `json:"bidIDGenerator,omitempty"` } type exchangeRequest struct { diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json new file mode 100644 index 00000000000..17d3a3ec4d8 --- /dev/null +++ b/exchange/exchangetest/bid-id-invalid.json @@ -0,0 +1,161 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": true + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + } + }, + "errors": { + "prebid": [ + { + "code": 999, + "message": "Error generating bid.ext.prebid.bidid" + } + ] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json new file mode 100644 index 00000000000..8da6410e8d8 --- /dev/null +++ b/exchange/exchangetest/bid-id-valid.json @@ -0,0 +1,154 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "prebid": { + "bidid": "mock_uuid", + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 6e29ccdf749..13afb8409af 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -1,6 +1,10 @@ { "events_enabled": false, "start_time_ms": 1234567890, + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -105,6 +109,7 @@ "crid": "creative-4", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video" } } @@ -116,7 +121,7 @@ "bid": [ { "id": "winning-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/winning-bid", "impid": "my-imp-id", "price": 0.71, @@ -125,6 +130,7 @@ "crid": "creative-1", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -139,7 +145,7 @@ }, { "id": "losing-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/losing-bid", "impid": "my-imp-id", "price": 0.21, @@ -148,6 +154,7 @@ "crid": "creative-2", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video" } } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 2cdf403439f..add3b19e0b2 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -95,6 +95,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), UsersyncIfAmbiguous: false, categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 7d787cf83a6..535ac76be04 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -21,6 +21,7 @@ type ExtBidPrebid struct { Type BidType `json:"type"` Video *ExtBidPrebidVideo `json:"video,omitempty"` Events *ExtBidPrebidEvents `json:"events,omitempty"` + BidId string `json:"bidid,omitempty"` } // ExtBidPrebidCache defines the contract for bidresponse.seatbid.bid[i].ext.prebid.cache From adb404499fd82fcba5affef8171edb674b669297 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 8 Apr 2021 15:07:41 -0400 Subject: [PATCH 372/603] Basic GDPR enforcement for specific publisher-vendors. (#1782) --- config/accounts.go | 5 ++-- endpoints/auction_test.go | 2 +- endpoints/cookie_sync_test.go | 2 +- endpoints/setuid_test.go | 2 +- exchange/exchange.go | 2 +- exchange/exchange_test.go | 2 +- exchange/utils.go | 14 ++++++++-- exchange/utils_test.go | 18 +++++++------ gdpr/gdpr.go | 2 +- gdpr/impl.go | 39 ++++++++++++++++------------ gdpr/impl_test.go | 49 +++++++++++++++++++++-------------- 11 files changed, 83 insertions(+), 54 deletions(-) diff --git a/config/accounts.go b/config/accounts.go index 548092451c3..f7b380fca48 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -39,8 +39,9 @@ func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) // AccountGDPR represents account-specific GDPR configuration type AccountGDPR struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + BasicEnforcementVendors []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"` } // EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index e99856129f7..d806c937ee4 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -454,7 +454,7 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return m.allowPI, m.allowGeo, m.allowID, nil } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index a6352387786..d4b89a15118 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -254,6 +254,6 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 87c98f23ad1..0d68c15bea8 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -439,7 +439,7 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return g.allowPI, g.allowPI, g.allowPI, nil } diff --git a/exchange/exchange.go b/exchange/exchange.go index 8dd6a06b480..433c1214ccf 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -178,7 +178,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index beaa823d320..5c47fdb6052 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/errortypes" "io/ioutil" "net/http" "net/http/httptest" @@ -20,6 +19,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/errortypes" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" diff --git a/exchange/utils.go b/exchange/utils.go index f011669e1f2..039f9cb5395 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,8 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, usersyncIfAmbiguous bool, - privacyConfig config.Privacy) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + privacyConfig config.Privacy, + account *config.Account) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { impsByBidder, err := splitImps(req.BidRequest.Imp) if err != nil { @@ -122,8 +123,17 @@ func cleanOpenRTBRequests(ctx context.Context, // GDPR if gdprEnforced { + weakVendorEnforcement := false + if account != nil { + for _, vendor := range account.GDPR.BasicEnforcementVendors { + if vendor == string(bidderRequest.BidderCoreName) { + weakVendorEnforcement = true + break + } + } + } var publisherID = req.LegacyLabels.PubID - _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent) + _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) if err == nil { privacyEnforcement.GDPRGeo = !geo privacyEnforcement.GDPRID = !id diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 05e22548639..5f72c39c702 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -32,7 +32,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string) (bool, bool, bool, error) { +func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError } @@ -465,7 +465,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -622,7 +622,8 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{personalInfoAllowed: true}, true, - privacyConfig) + privacyConfig, + nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -680,7 +681,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -720,7 +721,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -827,7 +828,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1408,7 +1409,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1625,7 +1626,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { nil, &permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError}, test.userSyncIfAmbiguous, - privacyConfig) + privacyConfig, + nil) result := results[0] if test.expectError { diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 36dac06c0ab..616d0f0ae07 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -25,7 +25,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) + PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) } // Versions of the GDPR TCF technical specification. diff --git a/gdpr/impl.go b/gdpr/impl.go index 5b1349ffe60..55d1cd4aeb0 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -58,7 +58,12 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (allowPI bool, allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, + bidder openrtb_ext.BidderName, + PublisherID string, + gdprSignal Signal, + consent string, + weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok { return true, true, true, nil } @@ -74,7 +79,7 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowPI(ctx, id, consent) + return p.allowPI(ctx, id, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() @@ -122,7 +127,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen err := fmt.Errorf("Unable to access TCF2 parsed consent") return false, err } - return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess), nil + return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil } if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { return true, nil @@ -130,7 +135,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, bool, error) { +func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { return false, false, false, err @@ -142,9 +147,9 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent if parsedConsent.Version() == 2 { if p.cfg.TCF2.Enabled { - return p.allowPITCF2(parsedConsent, vendor, vendorID) + return p.allowPITCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) { + if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { return true, true, true, nil } } else { @@ -155,7 +160,7 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent return false, false, false, nil } -func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { consent, ok := parsedConsent.(tcf2.ConsentMetadata) err = nil allowPI = false @@ -166,12 +171,12 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a return } if p.cfg.TCF2.SpecialPurpose1.Enabled { - allowGeo = consent.SpecialFeatureOptIn(1) && vendor.SpecialPurpose(1) + allowGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) } else { allowGeo = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i)) { + if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { allowID = true break } @@ -179,13 +184,13 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a // Set to true so any purpose check can flip it to false allowPI = true if p.cfg.TCF2.Purpose1.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, weakVendorEnforcement) } if p.cfg.TCF2.Purpose2.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving, weakVendorEnforcement) } if p.cfg.TCF2.Purpose7.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance, weakVendorEnforcement) } return } @@ -194,7 +199,7 @@ const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose) bool { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool { if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } @@ -202,8 +207,8 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return false } - purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) - legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) + legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { return purposeAllowed @@ -260,7 +265,7 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) { +func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } @@ -275,6 +280,6 @@ func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return false, nil } -func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) { +func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return false, false, false, nil } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 737ed14a300..b13d469a955 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -175,13 +175,14 @@ func TestAllowPersonalInfo(t *testing.T) { consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" tests := []struct { - description string - bidderName openrtb_ext.BidderName - publisherID string - userSyncIfAmbiguous bool - gdpr Signal - consent string - allowPI bool + description string + bidderName openrtb_ext.BidderName + publisherID string + userSyncIfAmbiguous bool + gdpr Signal + consent string + allowPI bool + weakVendorEnforcement bool }{ { description: "Allow PI - Non standard publisher", @@ -285,7 +286,7 @@ func TestAllowPersonalInfo(t *testing.T) { for _, tt := range tests { perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous - allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent) + allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) assert.Nil(t, err, tt.description) assert.Equal(t, tt.allowPI, allowPI, tt.description) @@ -343,12 +344,13 @@ var tcf2Config = config.GDPR{ } type tcf2TestDef struct { - description string - bidder openrtb_ext.BidderName - consent string - allowPI bool - allowGeo bool - allowID bool + description string + bidder openrtb_ext.BidderName + consent string + allowPI bool + allowGeo bool + allowID bool + weakVendorEnforcement bool } func TestAllowPersonalInfoTCF2(t *testing.T) { @@ -381,6 +383,15 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { allowGeo: false, allowID: false, }, + { + description: "Appnexus vendor test, insufficient purposes claimed, basic enforcement", + bidder: openrtb_ext.BidderAppnexus, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowPI: true, + allowGeo: true, + allowID: true, + weakVendorEnforcement: true, + }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, @@ -411,7 +422,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -437,7 +448,7 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}} - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") assert.EqualValuesf(t, true, allowPI, "AllowPI failure") assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") @@ -491,7 +502,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -547,7 +558,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -604,7 +615,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) From 28de9fd0bcc0ec208bd142132a1cfe776386fb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 8 Apr 2021 21:41:58 +0200 Subject: [PATCH 373/603] New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior --- adapters/zemanta/params_test.go | 50 ++++++ adapters/zemanta/usersync.go | 12 ++ adapters/zemanta/usersync_test.go | 33 ++++ adapters/zemanta/zemanta.go | 147 +++++++++++++++++ adapters/zemanta/zemanta_test.go | 20 +++ .../zemanta/zemantatest/exemplary/banner.json | 133 ++++++++++++++++ .../zemanta/zemantatest/exemplary/native.json | 125 +++++++++++++++ .../zemantatest/params/race/banner.json | 10 ++ .../zemantatest/params/race/native.json | 10 ++ .../zemantatest/supplemental/app_request.json | 144 +++++++++++++++++ .../supplemental/optional_params.json | 148 ++++++++++++++++++ .../zemantatest/supplemental/status_204.json | 62 ++++++++ .../zemantatest/supplemental/status_400.json | 67 ++++++++ .../zemantatest/supplemental/status_418.json | 67 ++++++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_zemanta.go | 15 ++ static/bidder-info/zemanta.yaml | 12 ++ static/bidder-params/zemanta.json | 40 +++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1104 insertions(+) create mode 100644 adapters/zemanta/params_test.go create mode 100644 adapters/zemanta/usersync.go create mode 100644 adapters/zemanta/usersync_test.go create mode 100644 adapters/zemanta/zemanta.go create mode 100644 adapters/zemanta/zemanta_test.go create mode 100644 adapters/zemanta/zemantatest/exemplary/banner.json create mode 100644 adapters/zemanta/zemantatest/exemplary/native.json create mode 100644 adapters/zemanta/zemantatest/params/race/banner.json create mode 100644 adapters/zemanta/zemantatest/params/race/native.json create mode 100644 adapters/zemanta/zemantatest/supplemental/app_request.json create mode 100644 adapters/zemanta/zemantatest/supplemental/optional_params.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_204.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_400.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_418.json create mode 100644 openrtb_ext/imp_zemanta.go create mode 100644 static/bidder-info/zemanta.yaml create mode 100644 static/bidder-params/zemanta.json diff --git a/adapters/zemanta/params_test.go b/adapters/zemanta/params_test.go new file mode 100644 index 00000000000..54e67c57bf1 --- /dev/null +++ b/adapters/zemanta/params_test.go @@ -0,0 +1,50 @@ +package zemanta + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisher": {"id": "publisher-id"}}`, + `{"publisher": {"id": "publisher-id", "name": "publisher-name", "domain": "publisher-domain.com"}, "tagid": "tag-id", "bcat": ["bad-category"], "badv": ["bad-advertiser"]}`, +} + +var invalidParams = []string{ + `{"publisher": {"id": 1234}}`, + `{"publisher": {"id": "pub-id", "name": 1234}}`, + `{"publisher": {"id": "pub-id", "domain": 1234}}`, + `{"publisher": {"id": "pub-id"}, "tagid": 1234}`, + `{"publisher": {"id": "pub-id"}, "bcat": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "bcat": [1234]}`, + `{"publisher": {"id": "pub-id"}, "badv": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "badv": [1234]}`, +} diff --git a/adapters/zemanta/usersync.go b/adapters/zemanta/usersync.go new file mode 100644 index 00000000000..fec8a2feda8 --- /dev/null +++ b/adapters/zemanta/usersync.go @@ -0,0 +1,12 @@ +package zemanta + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewZemantaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("zemanta", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/zemanta/usersync_test.go b/adapters/zemanta/usersync_test.go new file mode 100644 index 00000000000..e56851be821 --- /dev/null +++ b/adapters/zemanta/usersync_test.go @@ -0,0 +1,33 @@ +package zemanta + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSyncer(t *testing.T) { + syncURL := "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewZemantaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Consent: "C", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr=A&gdpr_consent=B&us_privacy=C&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) +} diff --git a/adapters/zemanta/zemanta.go b/adapters/zemanta/zemanta.go new file mode 100644 index 00000000000..ae547eb91aa --- /dev/null +++ b/adapters/zemanta/zemanta.go @@ -0,0 +1,147 @@ +package zemanta + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Zemanta adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + reqCopy := *request + + var errs []error + var zemantaExt openrtb_ext.ExtImpZemanta + for i := 0; i < len(reqCopy.Imp); i++ { + imp := reqCopy.Imp[i] + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + if err := json.Unmarshal(bidderExt.Bidder, &zemantaExt); err != nil { + errs = append(errs, err) + continue + } + imp.TagID = zemantaExt.TagId + reqCopy.Imp[i] = imp + } + + publisher := &openrtb2.Publisher{ + ID: zemantaExt.Publisher.Id, + Name: zemantaExt.Publisher.Name, + Domain: zemantaExt.Publisher.Domain, + } + if reqCopy.Site != nil { + siteCopy := *reqCopy.Site + siteCopy.Publisher = publisher + reqCopy.Site = &siteCopy + } else if reqCopy.App != nil { + appCopy := *reqCopy.App + appCopy.Publisher = publisher + reqCopy.App = &appCopy + } + + if zemantaExt.BCat != nil { + reqCopy.BCat = zemantaExt.BCat + } + if zemantaExt.BAdv != nil { + reqCopy.BAdv = zemantaExt.BAdv + } + + requestJSON, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + var errs []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } + + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner impression \"%s\" ", impID), + } +} diff --git a/adapters/zemanta/zemanta_test.go b/adapters/zemanta/zemanta_test.go new file mode 100644 index 00000000000..e63eb51005c --- /dev/null +++ b/adapters/zemanta/zemanta_test.go @@ -0,0 +1,20 @@ +package zemanta + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderZemanta, config.Adapter{ + Endpoint: "http://example.com/bid"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "zemantatest", bidder) +} diff --git a/adapters/zemanta/zemantatest/exemplary/banner.json b/adapters/zemanta/zemantatest/exemplary/banner.json new file mode 100644 index 00000000000..16d52cf1c0f --- /dev/null +++ b/adapters/zemanta/zemantatest/exemplary/banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/exemplary/native.json b/adapters/zemanta/zemantatest/exemplary/native.json new file mode 100644 index 00000000000..d32fd60382e --- /dev/null +++ b/adapters/zemanta/zemantatest/exemplary/native.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/params/race/banner.json b/adapters/zemanta/zemantatest/params/race/banner.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/zemanta/zemantatest/params/race/banner.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/zemanta/zemantatest/params/race/native.json b/adapters/zemanta/zemantatest/params/race/native.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/zemanta/zemantatest/params/race/native.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/zemanta/zemantatest/supplemental/app_request.json b/adapters/zemanta/zemantatest/supplemental/app_request.json new file mode 100644 index 00000000000..c8e7c4cf69f --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/app_request.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "pub-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/optional_params.json b/adapters/zemanta/zemantatest/supplemental/optional_params.json new file mode 100644 index 00000000000..a69ceaa0c85 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/optional_params.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_204.json b/adapters/zemanta/zemantatest/supplemental/status_204.json new file mode 100644 index 00000000000..9f668736953 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_204.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_400.json b/adapters/zemanta/zemantatest/supplemental/status_400.json new file mode 100644 index 00000000000..441162070d8 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_418.json b/adapters/zemanta/zemantatest/supplemental/status_418.json new file mode 100644 index 00000000000..08e26804806 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_418.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 7b78fb26bde..bda531f2460 100644 --- a/config/config.go +++ b/config/config.go @@ -649,6 +649,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZemanta, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBetween, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_ID%7D") } @@ -912,6 +913,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") + v.SetDefault("adapters.zemanta.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 0ea28f8a610..801d4023d02 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -103,6 +103,7 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" + "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, + openrtb_ext.BidderZemanta: zemanta.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 4d95babc988..8cc1bbd9547 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -177,6 +177,7 @@ const ( BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" + BidderZemanta BidderName = "zemanta" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -287,6 +288,7 @@ func CoreBidderNames() []BidderName { BidderYieldlab, BidderYieldmo, BidderYieldone, + BidderZemanta, BidderZeroClickFraud, } } diff --git a/openrtb_ext/imp_zemanta.go b/openrtb_ext/imp_zemanta.go new file mode 100644 index 00000000000..5baab6d2fd4 --- /dev/null +++ b/openrtb_ext/imp_zemanta.go @@ -0,0 +1,15 @@ +package openrtb_ext + +// ExtImpZemanta defines the contract for bidrequest.imp[i].ext.zemanta +type ExtImpZemanta struct { + Publisher ExtImpZemantaPublisher `json:"publisher"` + TagId string `json:"tagid"` + BCat []string `json:"bcat"` + BAdv []string `json:"badv"` +} + +type ExtImpZemantaPublisher struct { + Id string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} diff --git a/static/bidder-info/zemanta.yaml b/static/bidder-info/zemanta.yaml new file mode 100644 index 00000000000..e38ec915f49 --- /dev/null +++ b/static/bidder-info/zemanta.yaml @@ -0,0 +1,12 @@ +maintainer: + email: prog-ops-team@outbrain.com +gvlVendorID: 164 +capabilities: + app: + mediaTypes: + - banner + - native + site: + mediaTypes: + - banner + - native diff --git a/static/bidder-params/zemanta.json b/static/bidder-params/zemanta.json new file mode 100644 index 00000000000..b122c7dea4b --- /dev/null +++ b/static/bidder-params/zemanta.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Zemanta Adapter Params", + "description": "A schema which validates params accepted by the Zemanta adapter", + + "type": "object", + "properties": { + "publisher": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "domain": { + "type": "string" + } + }, + "required": ["id"] + }, + "tagid": { + "type": "string" + }, + "bcat": { + "type": "array", + "items": { + "type": "string" + } + }, + "badv": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["publisher"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index f01e4e4b917..30c0ebf5f37 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -87,6 +87,7 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" + "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -180,6 +181,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderZemanta, zemanta.NewZemantaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBetween, between.NewBetweenSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index a389a007306..325fd9b551c 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -96,6 +96,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderYieldmo): syncConfig, string(openrtb_ext.BidderYieldone): syncConfig, + string(openrtb_ext.BidderZemanta): syncConfig, string(openrtb_ext.BidderZeroClickFraud): syncConfig, }, } From aad97984cce29249caea5b1cbe4b05ca0c8e892b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Fri, 9 Apr 2021 20:29:45 +0200 Subject: [PATCH 374/603] Zemanta: Rename Adapter To Outbrain (#1797) --- .../zemanta.go => outbrain/outbrain.go} | 24 +++++++++---------- .../outbrain_test.go} | 6 ++--- .../outbraintest}/exemplary/banner.json | 0 .../outbraintest}/exemplary/native.json | 0 .../outbraintest}/params/race/banner.json | 0 .../outbraintest}/params/race/native.json | 0 .../supplemental/app_request.json | 0 .../supplemental/optional_params.json | 0 .../supplemental/status_204.json | 0 .../supplemental/status_400.json | 0 .../supplemental/status_418.json | 0 adapters/{zemanta => outbrain}/params_test.go | 6 ++--- adapters/outbrain/usersync.go | 12 ++++++++++ .../{zemanta => outbrain}/usersync_test.go | 4 ++-- adapters/zemanta/usersync.go | 12 ---------- config/config.go | 4 ++-- exchange/adapter_builders.go | 4 ++-- openrtb_ext/bidders.go | 4 ++-- openrtb_ext/imp_outbrain.go | 15 ++++++++++++ openrtb_ext/imp_zemanta.go | 15 ------------ .../{zemanta.yaml => outbrain.yaml} | 0 .../{zemanta.json => outbrain.json} | 4 ++-- usersync/usersyncers/syncer.go | 4 ++-- usersync/usersyncers/syncer_test.go | 2 +- 24 files changed, 58 insertions(+), 58 deletions(-) rename adapters/{zemanta/zemanta.go => outbrain/outbrain.go} (86%) rename adapters/{zemanta/zemanta_test.go => outbrain/outbrain_test.go} (69%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/exemplary/banner.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/exemplary/native.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/params/race/banner.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/params/race/native.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/supplemental/app_request.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/supplemental/optional_params.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/supplemental/status_204.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/supplemental/status_400.json (100%) rename adapters/{zemanta/zemantatest => outbrain/outbraintest}/supplemental/status_418.json (100%) rename adapters/{zemanta => outbrain}/params_test.go (87%) create mode 100644 adapters/outbrain/usersync.go rename adapters/{zemanta => outbrain}/usersync_test.go (93%) delete mode 100644 adapters/zemanta/usersync.go create mode 100644 openrtb_ext/imp_outbrain.go delete mode 100644 openrtb_ext/imp_zemanta.go rename static/bidder-info/{zemanta.yaml => outbrain.yaml} (100%) rename static/bidder-params/{zemanta.json => outbrain.json} (92%) diff --git a/adapters/zemanta/zemanta.go b/adapters/outbrain/outbrain.go similarity index 86% rename from adapters/zemanta/zemanta.go rename to adapters/outbrain/outbrain.go index ae547eb91aa..c270dcf2e84 100644 --- a/adapters/zemanta/zemanta.go +++ b/adapters/outbrain/outbrain.go @@ -1,4 +1,4 @@ -package zemanta +package outbrain import ( "encoding/json" @@ -16,7 +16,7 @@ type adapter struct { endpoint string } -// Builder builds a new instance of the Zemanta adapter for the given bidder with the given config. +// Builder builds a new instance of the Outbrain adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, @@ -28,7 +28,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte reqCopy := *request var errs []error - var zemantaExt openrtb_ext.ExtImpZemanta + var outbrainExt openrtb_ext.ExtImpOutbrain for i := 0; i < len(reqCopy.Imp); i++ { imp := reqCopy.Imp[i] @@ -37,18 +37,18 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errs = append(errs, err) continue } - if err := json.Unmarshal(bidderExt.Bidder, &zemantaExt); err != nil { + if err := json.Unmarshal(bidderExt.Bidder, &outbrainExt); err != nil { errs = append(errs, err) continue } - imp.TagID = zemantaExt.TagId + imp.TagID = outbrainExt.TagId reqCopy.Imp[i] = imp } publisher := &openrtb2.Publisher{ - ID: zemantaExt.Publisher.Id, - Name: zemantaExt.Publisher.Name, - Domain: zemantaExt.Publisher.Domain, + ID: outbrainExt.Publisher.Id, + Name: outbrainExt.Publisher.Name, + Domain: outbrainExt.Publisher.Domain, } if reqCopy.Site != nil { siteCopy := *reqCopy.Site @@ -60,11 +60,11 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte reqCopy.App = &appCopy } - if zemantaExt.BCat != nil { - reqCopy.BCat = zemantaExt.BCat + if outbrainExt.BCat != nil { + reqCopy.BCat = outbrainExt.BCat } - if zemantaExt.BAdv != nil { - reqCopy.BAdv = zemantaExt.BAdv + if outbrainExt.BAdv != nil { + reqCopy.BAdv = outbrainExt.BAdv } requestJSON, err := json.Marshal(reqCopy) diff --git a/adapters/zemanta/zemanta_test.go b/adapters/outbrain/outbrain_test.go similarity index 69% rename from adapters/zemanta/zemanta_test.go rename to adapters/outbrain/outbrain_test.go index e63eb51005c..533bad388ce 100644 --- a/adapters/zemanta/zemanta_test.go +++ b/adapters/outbrain/outbrain_test.go @@ -1,4 +1,4 @@ -package zemanta +package outbrain import ( "testing" @@ -9,12 +9,12 @@ import ( ) func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderZemanta, config.Adapter{ + bidder, buildErr := Builder(openrtb_ext.BidderOutbrain, config.Adapter{ Endpoint: "http://example.com/bid"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } - adapterstest.RunJSONBidderTest(t, "zemantatest", bidder) + adapterstest.RunJSONBidderTest(t, "outbraintest", bidder) } diff --git a/adapters/zemanta/zemantatest/exemplary/banner.json b/adapters/outbrain/outbraintest/exemplary/banner.json similarity index 100% rename from adapters/zemanta/zemantatest/exemplary/banner.json rename to adapters/outbrain/outbraintest/exemplary/banner.json diff --git a/adapters/zemanta/zemantatest/exemplary/native.json b/adapters/outbrain/outbraintest/exemplary/native.json similarity index 100% rename from adapters/zemanta/zemantatest/exemplary/native.json rename to adapters/outbrain/outbraintest/exemplary/native.json diff --git a/adapters/zemanta/zemantatest/params/race/banner.json b/adapters/outbrain/outbraintest/params/race/banner.json similarity index 100% rename from adapters/zemanta/zemantatest/params/race/banner.json rename to adapters/outbrain/outbraintest/params/race/banner.json diff --git a/adapters/zemanta/zemantatest/params/race/native.json b/adapters/outbrain/outbraintest/params/race/native.json similarity index 100% rename from adapters/zemanta/zemantatest/params/race/native.json rename to adapters/outbrain/outbraintest/params/race/native.json diff --git a/adapters/zemanta/zemantatest/supplemental/app_request.json b/adapters/outbrain/outbraintest/supplemental/app_request.json similarity index 100% rename from adapters/zemanta/zemantatest/supplemental/app_request.json rename to adapters/outbrain/outbraintest/supplemental/app_request.json diff --git a/adapters/zemanta/zemantatest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json similarity index 100% rename from adapters/zemanta/zemantatest/supplemental/optional_params.json rename to adapters/outbrain/outbraintest/supplemental/optional_params.json diff --git a/adapters/zemanta/zemantatest/supplemental/status_204.json b/adapters/outbrain/outbraintest/supplemental/status_204.json similarity index 100% rename from adapters/zemanta/zemantatest/supplemental/status_204.json rename to adapters/outbrain/outbraintest/supplemental/status_204.json diff --git a/adapters/zemanta/zemantatest/supplemental/status_400.json b/adapters/outbrain/outbraintest/supplemental/status_400.json similarity index 100% rename from adapters/zemanta/zemantatest/supplemental/status_400.json rename to adapters/outbrain/outbraintest/supplemental/status_400.json diff --git a/adapters/zemanta/zemantatest/supplemental/status_418.json b/adapters/outbrain/outbraintest/supplemental/status_418.json similarity index 100% rename from adapters/zemanta/zemantatest/supplemental/status_418.json rename to adapters/outbrain/outbraintest/supplemental/status_418.json diff --git a/adapters/zemanta/params_test.go b/adapters/outbrain/params_test.go similarity index 87% rename from adapters/zemanta/params_test.go rename to adapters/outbrain/params_test.go index 54e67c57bf1..a8d81d6234d 100644 --- a/adapters/zemanta/params_test.go +++ b/adapters/outbrain/params_test.go @@ -1,4 +1,4 @@ -package zemanta +package outbrain import ( "encoding/json" @@ -14,7 +14,7 @@ func TestValidParams(t *testing.T) { } for _, p := range validParams { - if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err != nil { + if err := validator.Validate(openrtb_ext.BidderOutbrain, json.RawMessage(p)); err != nil { t.Errorf("Schema rejected valid params: %s", p) } } @@ -27,7 +27,7 @@ func TestInvalidParams(t *testing.T) { } for _, p := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err == nil { + if err := validator.Validate(openrtb_ext.BidderOutbrain, json.RawMessage(p)); err == nil { t.Errorf("Schema allowed invalid params: %s", p) } } diff --git a/adapters/outbrain/usersync.go b/adapters/outbrain/usersync.go new file mode 100644 index 00000000000..7dd60306c60 --- /dev/null +++ b/adapters/outbrain/usersync.go @@ -0,0 +1,12 @@ +package outbrain + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewOutbrainSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("outbrain", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/zemanta/usersync_test.go b/adapters/outbrain/usersync_test.go similarity index 93% rename from adapters/zemanta/usersync_test.go rename to adapters/outbrain/usersync_test.go index e56851be821..f531834fc48 100644 --- a/adapters/zemanta/usersync_test.go +++ b/adapters/outbrain/usersync_test.go @@ -1,4 +1,4 @@ -package zemanta +package outbrain import ( "testing" @@ -16,7 +16,7 @@ func TestSyncer(t *testing.T) { template.New("sync-template").Parse(syncURL), ) - syncer := NewZemantaSyncer(syncURLTemplate) + syncer := NewOutbrainSyncer(syncURLTemplate) syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ GDPR: gdpr.Policy{ Signal: "A", diff --git a/adapters/zemanta/usersync.go b/adapters/zemanta/usersync.go deleted file mode 100644 index fec8a2feda8..00000000000 --- a/adapters/zemanta/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package zemanta - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewZemantaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("zemanta", temp, adapters.SyncTypeRedirect) -} diff --git a/config/config.go b/config/config.go index bda531f2460..b300bc58e49 100644 --- a/config/config.go +++ b/config/config.go @@ -622,6 +622,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOutbrain, "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doutbrain%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") @@ -649,7 +650,6 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZemanta, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBetween, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_ID%7D") } @@ -876,6 +876,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") + v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.pangle.disabled", true) v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") @@ -913,7 +914,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") - v.SetDefault("adapters.zemanta.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 801d4023d02..307a07fafd7 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -70,6 +70,7 @@ import ( "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/orbidder" + "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" @@ -103,7 +104,6 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -182,6 +182,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, + openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, @@ -216,7 +217,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, - openrtb_ext.BidderZemanta: zemanta.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8cc1bbd9547..f48cb23047a 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -143,6 +143,7 @@ const ( BidderOneTag BidderName = "onetag" BidderOpenx BidderName = "openx" BidderOrbidder BidderName = "orbidder" + BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" @@ -177,7 +178,6 @@ const ( BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" - BidderZemanta BidderName = "zemanta" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -254,6 +254,7 @@ func CoreBidderNames() []BidderName { BidderOneTag, BidderOpenx, BidderOrbidder, + BidderOutbrain, BidderPangle, BidderPubmatic, BidderPubnative, @@ -288,7 +289,6 @@ func CoreBidderNames() []BidderName { BidderYieldlab, BidderYieldmo, BidderYieldone, - BidderZemanta, BidderZeroClickFraud, } } diff --git a/openrtb_ext/imp_outbrain.go b/openrtb_ext/imp_outbrain.go new file mode 100644 index 00000000000..634f29481c1 --- /dev/null +++ b/openrtb_ext/imp_outbrain.go @@ -0,0 +1,15 @@ +package openrtb_ext + +// ExtImpOutbrain defines the contract for bidrequest.imp[i].ext.outbrain +type ExtImpOutbrain struct { + Publisher ExtImpOutbrainPublisher `json:"publisher"` + TagId string `json:"tagid"` + BCat []string `json:"bcat"` + BAdv []string `json:"badv"` +} + +type ExtImpOutbrainPublisher struct { + Id string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} diff --git a/openrtb_ext/imp_zemanta.go b/openrtb_ext/imp_zemanta.go deleted file mode 100644 index 5baab6d2fd4..00000000000 --- a/openrtb_ext/imp_zemanta.go +++ /dev/null @@ -1,15 +0,0 @@ -package openrtb_ext - -// ExtImpZemanta defines the contract for bidrequest.imp[i].ext.zemanta -type ExtImpZemanta struct { - Publisher ExtImpZemantaPublisher `json:"publisher"` - TagId string `json:"tagid"` - BCat []string `json:"bcat"` - BAdv []string `json:"badv"` -} - -type ExtImpZemantaPublisher struct { - Id string `json:"id"` - Name string `json:"name"` - Domain string `json:"domain"` -} diff --git a/static/bidder-info/zemanta.yaml b/static/bidder-info/outbrain.yaml similarity index 100% rename from static/bidder-info/zemanta.yaml rename to static/bidder-info/outbrain.yaml diff --git a/static/bidder-params/zemanta.json b/static/bidder-params/outbrain.json similarity index 92% rename from static/bidder-params/zemanta.json rename to static/bidder-params/outbrain.json index b122c7dea4b..8cfb8bbdf28 100644 --- a/static/bidder-params/zemanta.json +++ b/static/bidder-params/outbrain.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Zemanta Adapter Params", - "description": "A schema which validates params accepted by the Zemanta adapter", + "title": "Outbrain Adapter Params", + "description": "A schema which validates params accepted by the Outbrain adapter", "type": "object", "properties": { diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 30c0ebf5f37..104312cc164 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -60,6 +60,7 @@ import ( "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openx" + "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rhythmone" @@ -87,7 +88,6 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -153,6 +153,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNoBid, nobid.NewNoBidSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOutbrain, outbrain.NewOutbrainSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) @@ -181,7 +182,6 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderZemanta, zemanta.NewZemantaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBetween, between.NewBetweenSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 325fd9b551c..e0dc3dd2896 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -69,6 +69,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderNoBid): syncConfig, string(openrtb_ext.BidderOneTag): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, + string(openrtb_ext.BidderOutbrain): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig, string(openrtb_ext.BidderRhythmone): syncConfig, @@ -96,7 +97,6 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderYieldmo): syncConfig, string(openrtb_ext.BidderYieldone): syncConfig, - string(openrtb_ext.BidderZemanta): syncConfig, string(openrtb_ext.BidderZeroClickFraud): syncConfig, }, } From c0a85f747534005ddf3944606c0419c8674f8330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Wed, 14 Apr 2021 17:38:02 +0200 Subject: [PATCH 375/603] Update openrtb library to v15 (#1802) --- adapters/33across/33across.go | 2 +- adapters/acuityads/acuityads.go | 2 +- adapters/adapterstest/adapter_test_util.go | 2 +- adapters/adapterstest/test_json.go | 2 +- adapters/adform/adform.go | 2 +- adapters/adform/adform_test.go | 2 +- adapters/adgeneration/adgeneration.go | 2 +- adapters/adgeneration/adgeneration_test.go | 2 +- adapters/adhese/adhese.go | 2 +- adapters/adhese/utils.go | 2 +- adapters/adkernel/adkernel.go | 2 +- adapters/adkernelAdn/adkernelAdn.go | 2 +- adapters/adman/adman.go | 2 +- adapters/admixer/admixer.go | 2 +- adapters/adocean/adocean.go | 2 +- adapters/adoppler/adoppler.go | 2 +- adapters/adot/adot.go | 2 +- adapters/adot/adot_test.go | 2 +- adapters/adpone/adpone.go | 2 +- adapters/adprime/adprime.go | 2 +- adapters/adtarget/adtarget.go | 2 +- adapters/adtelligent/adtelligent.go | 2 +- adapters/advangelists/advangelists.go | 2 +- adapters/adyoulike/adyoulike.go | 2 +- adapters/aja/aja.go | 2 +- adapters/amx/amx.go | 2 +- adapters/amx/amx_test.go | 2 +- adapters/applogy/applogy.go | 2 +- adapters/appnexus/appnexus.go | 2 +- adapters/appnexus/appnexus_test.go | 2 +- adapters/audienceNetwork/facebook.go | 2 +- adapters/avocet/avocet.go | 2 +- adapters/avocet/avocet_test.go | 2 +- adapters/beachfront/beachfront.go | 2 +- adapters/beintoo/beintoo.go | 2 +- adapters/between/between.go | 2 +- adapters/bidder.go | 2 +- adapters/bidmachine/bidmachine.go | 2 +- adapters/brightroll/brightroll.go | 2 +- adapters/colossus/colossus.go | 2 +- adapters/connectad/connectad.go | 2 +- adapters/consumable/adtypes.go | 2 +- adapters/consumable/consumable.go | 2 +- adapters/conversant/cnvr_legacy.go | 2 +- adapters/conversant/cnvr_legacy_test.go | 2 +- adapters/conversant/conversant.go | 2 +- adapters/cpmstar/cpmstar.go | 2 +- adapters/criteo/criteo.go | 2 +- adapters/criteo/models.go | 2 +- adapters/criteo/models_test.go | 2 +- adapters/datablocks/datablocks.go | 2 +- adapters/decenterads/decenterads.go | 2 +- adapters/deepintent/deepintent.go | 2 +- adapters/dmx/dmx.go | 2 +- adapters/dmx/dmx_test.go | 2 +- adapters/emx_digital/emx_digital.go | 2 +- adapters/engagebdr/engagebdr.go | 2 +- adapters/eplanning/eplanning.go | 2 +- adapters/epom/epom.go | 2 +- adapters/gamma/gamma.go | 2 +- adapters/gamoshi/gamoshi.go | 2 +- adapters/grid/grid.go | 2 +- adapters/gumgum/gumgum.go | 2 +- adapters/improvedigital/improvedigital.go | 2 +- adapters/infoawarebidder.go | 2 +- adapters/infoawarebidder_test.go | 2 +- adapters/inmobi/inmobi.go | 2 +- adapters/invibes/invibes.go | 2 +- adapters/ix/ix.go | 2 +- adapters/ix/ix_test.go | 2 +- adapters/jixie/jixie.go | 2 +- adapters/kidoz/kidoz.go | 2 +- adapters/kidoz/kidoz_test.go | 2 +- adapters/krushmedia/krushmedia.go | 2 +- adapters/kubient/kubient.go | 2 +- adapters/lifestreet/lifestreet.go | 2 +- adapters/lifestreet/lifestreet_test.go | 2 +- adapters/lockerdome/lockerdome.go | 2 +- adapters/logicad/logicad.go | 2 +- adapters/lunamedia/lunamedia.go | 2 +- adapters/marsmedia/marsmedia.go | 2 +- adapters/mgid/mgid.go | 2 +- adapters/mobfoxpb/mobfoxpb.go | 2 +- adapters/mobilefuse/mobilefuse.go | 2 +- adapters/nanointeractive/nanointeractive.go | 2 +- adapters/ninthdecimal/ninthdecimal.go | 2 +- adapters/nobid/nobid.go | 2 +- adapters/onetag/onetag.go | 2 +- adapters/openrtb_util.go | 2 +- adapters/openrtb_util_test.go | 2 +- adapters/openx/openx.go | 2 +- adapters/openx/openx_test.go | 2 +- adapters/orbidder/orbidder.go | 2 +- adapters/outbrain/outbrain.go | 2 +- adapters/pangle/pangle.go | 2 +- adapters/pubmatic/pubmatic.go | 2 +- adapters/pubmatic/pubmatic_test.go | 2 +- adapters/pubnative/pubnative.go | 2 +- adapters/pulsepoint/pulsepoint.go | 2 +- adapters/pulsepoint/pulsepoint_test.go | 2 +- adapters/revcontent/revcontent.go | 2 +- adapters/rhythmone/rhythmone.go | 2 +- adapters/rtbhouse/rtbhouse.go | 2 +- adapters/rubicon/rubicon.go | 2 +- adapters/rubicon/rubicon_test.go | 2 +- adapters/sharethrough/butler.go | 2 +- adapters/sharethrough/butler_test.go | 2 +- adapters/sharethrough/sharethrough.go | 2 +- adapters/sharethrough/sharethrough_test.go | 2 +- adapters/sharethrough/utils.go | 2 +- adapters/sharethrough/utils_test.go | 2 +- adapters/silvermob/silvermob.go | 2 +- adapters/smaato/smaato.go | 2 +- adapters/smartadserver/smartadserver.go | 2 +- adapters/smartrtb/smartrtb.go | 2 +- adapters/smartyads/smartyads.go | 2 +- adapters/somoaudience/somoaudience.go | 2 +- adapters/sonobi/sonobi.go | 2 +- adapters/sovrn/sovrn.go | 2 +- adapters/sovrn/sovrn_test.go | 2 +- adapters/synacormedia/synacormedia.go | 2 +- adapters/tappx/tappx.go | 2 +- adapters/telaria/telaria.go | 2 +- adapters/triplelift/triplelift.go | 2 +- .../triplelift_native/triplelift_native.go | 2 +- adapters/ucfunnel/ucfunnel.go | 2 +- adapters/ucfunnel/ucfunnel_test.go | 2 +- adapters/unicorn/unicorn.go | 2 +- adapters/unruly/unruly.go | 2 +- adapters/unruly/unruly_test.go | 2 +- adapters/valueimpression/valueimpression.go | 2 +- adapters/verizonmedia/verizonmedia.go | 2 +- adapters/visx/visx.go | 2 +- adapters/vrtcal/vrtcal.go | 2 +- adapters/yeahmobi/yeahmobi.go | 2 +- adapters/yieldlab/yieldlab.go | 2 +- adapters/yieldmo/yieldmo.go | 2 +- adapters/yieldone/yieldone.go | 2 +- adapters/zeroclickfraud/zeroclickfraud.go | 2 +- amp/parse.go | 2 +- amp/parse_test.go | 2 +- analytics/config/config_test.go | 2 +- analytics/core.go | 2 +- analytics/filesystem/file_module_test.go | 2 +- analytics/pubstack/helpers/json_test.go | 2 +- analytics/pubstack/pubstack_module_test.go | 2 +- endpoints/auction_test.go | 2 +- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/amp_auction_test.go | 2 +- endpoints/openrtb2/auction.go | 6 +- endpoints/openrtb2/auction_test.go | 2 +- endpoints/openrtb2/interstitial.go | 2 +- endpoints/openrtb2/interstitial_test.go | 2 +- endpoints/openrtb2/video_auction.go | 2 +- endpoints/openrtb2/video_auction_test.go | 2 +- exchange/adapter_util_test.go | 2 +- exchange/auction.go | 2 +- exchange/auction_test.go | 2 +- exchange/bidder.go | 20 +++-- exchange/bidder_test.go | 54 ++++++++++-- exchange/bidder_validate_bids.go | 2 +- exchange/bidder_validate_bids_test.go | 2 +- exchange/events_test.go | 2 +- exchange/exchange.go | 2 +- exchange/exchange_test.go | 2 +- exchange/gdpr.go | 2 +- exchange/gdpr_test.go | 2 +- exchange/legacy.go | 2 +- exchange/legacy_test.go | 2 +- exchange/targeting.go | 2 +- exchange/targeting_test.go | 2 +- exchange/utils.go | 2 +- exchange/utils_test.go | 2 +- go.mod | 12 +-- go.sum | 84 +++++++++++++++---- openrtb_ext/bid_request_video.go | 2 +- openrtb_ext/deal_tier.go | 2 +- openrtb_ext/deal_tier_test.go | 2 +- openrtb_ext/response.go | 2 +- pbs/pbsrequest.go | 2 +- privacy/ccpa/consentwriter.go | 2 +- privacy/ccpa/consentwriter_test.go | 2 +- privacy/ccpa/parsedpolicy_test.go | 2 +- privacy/ccpa/policy.go | 2 +- privacy/ccpa/policy_test.go | 2 +- privacy/enforcement.go | 2 +- privacy/enforcement_test.go | 2 +- privacy/gdpr/consentwriter.go | 2 +- privacy/gdpr/consentwriter_test.go | 2 +- privacy/lmt/ios.go | 2 +- privacy/lmt/ios_test.go | 2 +- privacy/lmt/policy.go | 2 +- privacy/lmt/policy_test.go | 2 +- privacy/scrubber.go | 2 +- privacy/scrubber_test.go | 2 +- privacy/writer.go | 2 +- privacy/writer_test.go | 2 +- 197 files changed, 326 insertions(+), 234 deletions(-) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index bc7229eda99..fb329a76f21 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index a461dada391..da1cda3da77 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adapterstest/adapter_test_util.go b/adapters/adapterstest/adapter_test_util.go index 0c51a74e6b6..25d43a138fb 100644 --- a/adapters/adapterstest/adapter_test_util.go +++ b/adapters/adapterstest/adapter_test_util.go @@ -8,7 +8,7 @@ import ( "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // OrtbMockService Represents a scaffolded OpenRTB service. diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index e33dbddaecf..95319f1e328 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -7,7 +7,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index df294b56154..225c7af35d4 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -14,7 +14,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index f2056c499c5..70cd6883a4d 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/pbs" diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index fd87c6f4034..110ec5ce98a 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index 01fff96ede1..d5d93ac4e0b 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 773b3b98fd6..8b4d8c98afe 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -11,7 +11,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adhese/utils.go b/adapters/adhese/utils.go index c83f9ef1480..5a81bb83bd5 100644 --- a/adapters/adhese/utils.go +++ b/adapters/adhese/utils.go @@ -1,6 +1,6 @@ package adhese -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" type AdheseOriginData struct { Priority string `json:"priority"` diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 93fb3c6f93d..362307cce79 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 01024baad61..f6075f865e7 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 0eab6204b6d..808951d3aba 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index 86d7bfd9d5f..ec49950a17e 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 97ace405ab0..635cba8c9bc 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -13,7 +13,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 56fae4295b4..015f31c7d01 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -8,7 +8,7 @@ import ( "net/url" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index f4fd6c23bdb..f41beed8d21 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index 3cb3d680ebd..fbc7def4a74 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index ffc36cc5aba..972f1576ac1 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 6489c543d56..70fbd6b399b 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index b43e7e51b07..24befbfec0f 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index 6c7bc1f3378..244742db504 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 8a12089df90..b5b385f6c06 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index 4797341e7b5..4cccd5dc5bc 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -9,7 +9,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" ) diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 1ff942ce4de..6e77fdd5685 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/amx/amx.go b/adapters/amx/amx.go index 68f4ecdfbb4..737fac421f0 100644 --- a/adapters/amx/amx.go +++ b/adapters/amx/amx.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/amx/amx_test.go b/adapters/amx/amx_test.go index 77947c068d0..229889db7cf 100644 --- a/adapters/amx/amx_test.go +++ b/adapters/amx/amx_test.go @@ -6,7 +6,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go index 6edc8135516..b144c6b836f 100644 --- a/adapters/applogy/applogy.go +++ b/adapters/applogy/applogy.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index a384257d47b..046f5d312d7 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/pbs" diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index 076d93eacb6..ead45280aec 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/cache/dummycache" diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index f2aaaed8f88..5cbdbc90561 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go index b3cda3a1bdd..7ec4788e858 100644 --- a/adapters/avocet/avocet.go +++ b/adapters/avocet/avocet.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go index b256adc6f17..8dc3c333147 100644 --- a/adapters/avocet/avocet_test.go +++ b/adapters/avocet/avocet_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index ea4ff2c83e5..6bcabf4a39c 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go index 8b5698750e6..60022e73316 100644 --- a/adapters/beintoo/beintoo.go +++ b/adapters/beintoo/beintoo.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/between/between.go b/adapters/between/between.go index f1be7db5fc2..9d77a0413fd 100644 --- a/adapters/between/between.go +++ b/adapters/between/between.go @@ -8,7 +8,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/bidder.go b/adapters/bidder.go index a389299f888..fe40184e40f 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -5,7 +5,7 @@ import ( "encoding/json" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go index 3c9a5b679e2..14b72cc6c56 100644 --- a/adapters/bidmachine/bidmachine.go +++ b/adapters/bidmachine/bidmachine.go @@ -9,7 +9,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/brightroll/brightroll.go b/adapters/brightroll/brightroll.go index fe770b07d16..9a64db45f0b 100644 --- a/adapters/brightroll/brightroll.go +++ b/adapters/brightroll/brightroll.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go index 801b338478a..4308edb704e 100644 --- a/adapters/colossus/colossus.go +++ b/adapters/colossus/colossus.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 9192a520a05..9827ebcea7b 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/consumable/adtypes.go b/adapters/consumable/adtypes.go index 20b89bdfe7f..5c5bd425bbf 100644 --- a/adapters/consumable/adtypes.go +++ b/adapters/consumable/adtypes.go @@ -3,7 +3,7 @@ package consumable import ( "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) /* Turn array of openrtb formats into consumable's code*/ diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index d81bc20b0d3..c2e0c201187 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go index e29fb7c019b..eff1afc5d32 100644 --- a/adapters/conversant/cnvr_legacy.go +++ b/adapters/conversant/cnvr_legacy.go @@ -8,7 +8,7 @@ import ( "io/ioutil" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/pbs" diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go index ef8a780c746..e2000bc0dd1 100644 --- a/adapters/conversant/cnvr_legacy_test.go +++ b/adapters/conversant/cnvr_legacy_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index d742033437a..cdcf75665f3 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go index b3100222e98..86c87c4ccb6 100644 --- a/adapters/cpmstar/cpmstar.go +++ b/adapters/cpmstar/cpmstar.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index f045c9f009b..9f6ca4d74ec 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go index 64f6081d781..1ae51c12f8b 100644 --- a/adapters/criteo/models.go +++ b/adapters/criteo/models.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/criteo/models_test.go b/adapters/criteo/models_test.go index 9da7bda210b..e85fa4c1a5c 100644 --- a/adapters/criteo/models_test.go +++ b/adapters/criteo/models_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index 28b9c36360e..fbed456ace9 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go index ac60a4042ae..c94c461e367 100644 --- a/adapters/decenterads/decenterads.go +++ b/adapters/decenterads/decenterads.go @@ -7,7 +7,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 17168ae05f8..b5b0fd54c5d 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 280c7187ca8..7124d229347 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 4288f2fa940..a9f1e8bbc79 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/emx_digital/emx_digital.go b/adapters/emx_digital/emx_digital.go index 98a8b6eb943..75d74cf6cd3 100644 --- a/adapters/emx_digital/emx_digital.go +++ b/adapters/emx_digital/emx_digital.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/engagebdr/engagebdr.go b/adapters/engagebdr/engagebdr.go index 6c55b99987b..645773fe735 100644 --- a/adapters/engagebdr/engagebdr.go +++ b/adapters/engagebdr/engagebdr.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 2aa066ba2b2..1e593fb7d93 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -11,7 +11,7 @@ import ( "fmt" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/epom/epom.go b/adapters/epom/epom.go index b15c7b1cc04..a8336b64da6 100644 --- a/adapters/epom/epom.go +++ b/adapters/epom/epom.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index 1408fdeeede..a99a4643632 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/gamoshi/gamoshi.go b/adapters/gamoshi/gamoshi.go index 50b7e9d58a5..c3219c1b59d 100644 --- a/adapters/gamoshi/gamoshi.go +++ b/adapters/gamoshi/gamoshi.go @@ -7,7 +7,7 @@ import ( "strconv" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index 69bc1500bf9..2c2b291bab8 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 0334aad9d43..00927b9bf80 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index b942336b410..9b2d571d903 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 67227d822b3..6a07bda3a4d 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -3,7 +3,7 @@ package adapters import ( "fmt" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 60e5af041da..375248137ad 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index 5034f01d77b..a23472e8892 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/invibes/invibes.go b/adapters/invibes/invibes.go index fb4d7641d79..d29750c84bf 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -9,7 +9,7 @@ import ( "strings" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 038a2c146b9..38294cc587b 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -8,7 +8,7 @@ import ( "io/ioutil" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index 4c0515b4c6b..27d9370e1b7 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/jixie/jixie.go b/adapters/jixie/jixie.go index 565f8a588c3..b158dbba58e 100644 --- a/adapters/jixie/jixie.go +++ b/adapters/jixie/jixie.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index 670bfb80b49..43372dc2f39 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index 0de06392bba..3fd807ea454 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index 54e89200844..f1b80da701f 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go index 8b011b7f795..a99e9005105 100644 --- a/adapters/kubient/kubient.go +++ b/adapters/kubient/kubient.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/lifestreet/lifestreet.go b/adapters/lifestreet/lifestreet.go index 7114a9bbaac..14f6931751a 100644 --- a/adapters/lifestreet/lifestreet.go +++ b/adapters/lifestreet/lifestreet.go @@ -9,7 +9,7 @@ import ( "net/http" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/adapters" diff --git a/adapters/lifestreet/lifestreet_test.go b/adapters/lifestreet/lifestreet_test.go index 215f137ccf4..5c4f47fdff9 100644 --- a/adapters/lifestreet/lifestreet_test.go +++ b/adapters/lifestreet/lifestreet_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index 483ffbd5bd6..28c966be6de 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index bc45652d127..982723d0d0a 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index a8040365964..899269e661f 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index 20d0d1fc645..a63db09e208 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index 0917003ab82..95ede0ab5c4 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index d39fbfb74e9..7fcf416a480 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index 217f0f24b7b..47ee1cab743 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go index afe80bcbfee..a2ec89b0d5b 100644 --- a/adapters/nanointeractive/nanointeractive.go +++ b/adapters/nanointeractive/nanointeractive.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go index e819a2787a2..fc29b38cdab 100755 --- a/adapters/ninthdecimal/ninthdecimal.go +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index 7fc7669b7c4..f8db812d9ca 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index 6069682bc2a..1721d76df6a 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go index bc18b9ebf17..6aa07c6b764 100644 --- a/adapters/openrtb_util.go +++ b/adapters/openrtb_util.go @@ -3,7 +3,7 @@ package adapters import ( "encoding/json" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/pbs" ) diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index d859114b646..b7d03fbfc6c 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -5,7 +5,7 @@ import ( "encoding/json" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index c8c0de8d9d2..d53d405ba24 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index aaf7ecf85a2..ea90dc875da 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index b0b3bcfbb7a..5e8ea3db84f 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index c270dcf2e84..02a9784303c 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index cc215412a5f..a4694c71559 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index f3f5785492d..afc9263af4e 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 7e8f177a1ba..032a324d18a 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 35bc7553db3..8093c841fb2 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 35297d795a6..6b6b4305607 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index a16f9a0e470..fac0bfd4de8 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go index f3b24436d63..173917c2314 100644 --- a/adapters/revcontent/revcontent.go +++ b/adapters/revcontent/revcontent.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go index 096e9190622..de43537e55b 100644 --- a/adapters/rhythmone/rhythmone.go +++ b/adapters/rhythmone/rhythmone.go @@ -6,7 +6,7 @@ import ( "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index b2eccd76305..44adddee8fd 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index dff961cc79f..89d69522fe8 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 747758bc820..dc5b3a90423 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/adapters/adapterstest" diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index c9b9726ff4e..b34ae0844ab 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index 3b1f2159bb7..fbef417e530 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index 65afb21adb7..410ca391bd4 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -5,7 +5,7 @@ import ( "net/http" "regexp" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index 194894fd893..1cf45d1fde2 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -6,7 +6,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/sharethrough/utils.go b/adapters/sharethrough/utils.go index 4f27e738b99..e10a8f90b7b 100644 --- a/adapters/sharethrough/utils.go +++ b/adapters/sharethrough/utils.go @@ -13,7 +13,7 @@ import ( "time" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index fb199369e59..b842cf0b0c0 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -7,7 +7,7 @@ import ( "regexp" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index 47a762ecc04..8a5c6b259b6 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 41229574f9e..9aea2e1e614 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/smartadserver/smartadserver.go b/adapters/smartadserver/smartadserver.go index 1e1eeac4f95..3954a00b4d5 100644 --- a/adapters/smartadserver/smartadserver.go +++ b/adapters/smartadserver/smartadserver.go @@ -8,7 +8,7 @@ import ( "path" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index 548d4f36db5..e123d4eb6d2 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index e02ba4200f3..b5a09223eb0 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/somoaudience/somoaudience.go b/adapters/somoaudience/somoaudience.go index d799b5bb5f7..1e807bb2370 100644 --- a/adapters/somoaudience/somoaudience.go +++ b/adapters/somoaudience/somoaudience.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index c3527a6a73e..690d5f59f67 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index fd2acc1fcbd..be1c2221ae5 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index a7bd2a204b9..c3290c30a2b 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index f6ead542d2e..aec3169fe54 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -6,7 +6,7 @@ import ( "net/http" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 57abee63923..5970ccb6cfe 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -10,7 +10,7 @@ import ( "text/template" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index cda22e06937..e0a451a9e6c 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 2e2b525ea50..3cd651cee5c 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 3a4d0588e7a..d412def437f 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index f1d4fa9f175..1d3efc04451 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index c735cb567ce..95ac5985f56 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index b9741c8c9e3..8d1413f43dd 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 871af6df46d..0077fae4df5 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index b7c9a28eb47..445745c102d 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" diff --git a/adapters/valueimpression/valueimpression.go b/adapters/valueimpression/valueimpression.go index 1397f6e020d..aac8faab52c 100644 --- a/adapters/valueimpression/valueimpression.go +++ b/adapters/valueimpression/valueimpression.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/verizonmedia/verizonmedia.go b/adapters/verizonmedia/verizonmedia.go index edbb05a3a62..aa215c01691 100644 --- a/adapters/verizonmedia/verizonmedia.go +++ b/adapters/verizonmedia/verizonmedia.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 1f1fae0a484..8e2b10f7c32 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index ff5bc2dc733..e4986b2f6fb 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index 74208ca4c30..8a692d4ff2e 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -8,7 +8,7 @@ import ( "text/template" "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 447f8aa55fd..ee9170c25cf 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -11,7 +11,7 @@ import ( "golang.org/x/text/currency" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 6c9524a8823..7d7a8f22b01 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 813746e1d99..4e22b1446a7 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index ab1938fa518..cf27c83d4a7 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -7,7 +7,7 @@ import ( "strconv" "text/template" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" diff --git a/amp/parse.go b/amp/parse.go index 40604c774be..05e43aca1e2 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // Params defines the paramters of an AMP request. diff --git a/amp/parse_test.go b/amp/parse_test.go index 3a15e81ab46..91027f8e67c 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 1ec0c27e923..310dbe1a481 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/prebid/prebid-server/analytics" diff --git a/analytics/core.go b/analytics/core.go index e9403968c27..38c4f779327 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -3,7 +3,7 @@ package analytics import ( "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index cfb72f487b0..0c3d3c9e6ac 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/analytics" diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index ba97d60da18..7673b067f8a 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/usersync" ) diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 8cc572022ef..cb8f088d0bf 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index d806c937ee4..17ed7f74f45 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -10,7 +10,7 @@ import ( "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index cc8df0bf1b2..6767151f15f 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -14,7 +14,7 @@ import ( "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/amp" "github.com/prebid/prebid-server/analytics" diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 45bbb2dd3e5..079b9adb6d4 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -11,7 +11,7 @@ import ( "strconv" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 91acab33ea1..bc8946fa90c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -18,9 +18,9 @@ import ( "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v14/native1" - nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/native1" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + "github.com/mxmCherry/openrtb/v15/openrtb2" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 2deb5d1b762..75d0610cb34 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,7 +17,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/stored_requests" "github.com/buger/jsonparser" diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 67f9bc37b28..1aa2a7fc890 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 7691a6672e0..1d7ad9e3d6b 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index f46a8c2a1d5..2ab3bbb0829 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -17,7 +17,7 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/util/iputil" diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 16dc02ee97b..9ede7147686 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 73a456f54b1..c9f1907d314 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/lifestreet" diff --git a/exchange/auction.go b/exchange/auction.go index b3878709c29..3d733daaff8 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -11,7 +11,7 @@ import ( "time" uuid "github.com/gofrs/uuid" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" diff --git a/exchange/auction_test.go b/exchange/auction_test.go index bd32607bdf3..54f67eb8177 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -11,7 +11,7 @@ import ( "strconv" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" diff --git a/exchange/bidder.go b/exchange/bidder.go index 23b42d2a9d7..59ef92cbff4 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,9 +16,9 @@ import ( "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" - nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" - nativeResponse "github.com/mxmCherry/openrtb/v14/native1/response" - "github.com/mxmCherry/openrtb/v14/openrtb2" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -280,13 +280,16 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { if asset.Img != nil { - if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if asset.ID == nil { + return errors.New("Response Image asset doesn't have an ID") + } + if tempAsset, err := getAssetByID(*asset.ID, nativePayload.Assets); err == nil { if tempAsset.Img != nil { if tempAsset.Img.Type != 0 { asset.Img.Type = tempAsset.Img.Type } } else { - return fmt.Errorf("Response has an Image asset with ID:%d present that doesn't exist in the request", asset.ID) + return fmt.Errorf("Response has an Image asset with ID:%d present that doesn't exist in the request", *asset.ID) } } else { return err @@ -294,13 +297,16 @@ func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Requ } if asset.Data != nil { - if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if asset.ID == nil { + return errors.New("Response Data asset doesn't have an ID") + } + if tempAsset, err := getAssetByID(*asset.ID, nativePayload.Assets); err == nil { if tempAsset.Data != nil { if tempAsset.Data.Type != 0 { asset.Data.Type = tempAsset.Data.Type } } else { - return fmt.Errorf("Response has a Data asset with ID:%d present that doesn't exist in the request", asset.ID) + return fmt.Errorf("Response has a Data asset with ID:%d present that doesn't exist in the request", *asset.ID) } } else { return err diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 41ca420a433..6db249ec6ed 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -16,9 +16,9 @@ import ( "time" "github.com/golang/glog" - nativeRequests "github.com/mxmCherry/openrtb/v14/native1/request" - nativeResponse "github.com/mxmCherry/openrtb/v14/native1/response" - "github.com/mxmCherry/openrtb/v14/openrtb2" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -1258,7 +1258,7 @@ func TestSetAssetTypes(t *testing.T) { }{ { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1284,7 +1284,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 2, + ID: openrtb2.Int64Ptr(2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1310,7 +1310,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1330,7 +1330,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 2, + ID: openrtb2.Int64Ptr(2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1350,7 +1350,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1368,6 +1368,44 @@ func TestSetAssetTypes(t *testing.T) { expectedErr: "Response has an Image asset with ID:1 present that doesn't exist in the request", desc: "Assets with same ID in the req and resp are of different types", }, + { + respAsset: nativeResponse.Asset{ + Img: &nativeResponse.Image{ + URL: "http://some-url", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response Image asset doesn't have an ID", + desc: "Response Image without an ID", + }, + { + respAsset: nativeResponse.Asset{ + Data: &nativeResponse.Data{ + Label: "some label", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Data: &nativeRequests.Data{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response Data asset doesn't have an ID", + desc: "Response Data asset without an ID", + }, } for _, test := range testCases { diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index ad387cf4fcc..3d2eb0b8e42 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 97a11a6b743..3bb43559856 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/exchange/events_test.go b/exchange/events_test.go index bde2b235987..887122a687e 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -3,7 +3,7 @@ package exchange import ( "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/exchange.go b/exchange/exchange.go index 433c1214ccf..eaff759f619 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/stored_requests" "github.com/gofrs/uuid" diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 5c47fdb6052..c18f4533966 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" diff --git a/exchange/gdpr.go b/exchange/gdpr.go index a6b4a22dc8f..208ce0fdb0b 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,7 +3,7 @@ package exchange import ( "encoding/json" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/gdpr" ) diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index 420b299f2cc..e44dc9702fb 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/exchange/legacy.go b/exchange/legacy.go index ea8b65c28f0..3983ce132ed 100644 --- a/exchange/legacy.go +++ b/exchange/legacy.go @@ -6,7 +6,7 @@ import ( "errors" "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go index aa684287997..cbb5fda4fcc 100644 --- a/exchange/legacy_test.go +++ b/exchange/legacy_test.go @@ -11,7 +11,7 @@ import ( "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/openrtb_ext" diff --git a/exchange/targeting.go b/exchange/targeting.go index f79e05be930..f00e43d200c 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -3,7 +3,7 @@ package exchange import ( "strconv" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index add3b19e0b2..aa07ed0c77b 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" diff --git a/exchange/utils.go b/exchange/utils.go index 039f9cb5395..5bde1bd7889 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,7 +6,7 @@ import ( "fmt" "math/rand" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" "github.com/buger/jsonparser" diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5f72c39c702..55a0950aac6 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/gdpr" diff --git a/go.mod b/go.mod index 67f95057bee..585798cbe66 100644 --- a/go.mod +++ b/go.mod @@ -28,9 +28,7 @@ require ( github.com/mattn/go-colorable v0.1.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.0.0 // indirect - github.com/mxmCherry/openrtb/v14 v14.0.0 - github.com/onsi/ginkgo v1.10.1 // indirect - github.com/onsi/gomega v1.7.0 // indirect + github.com/mxmCherry/openrtb/v15 v15.0.0 github.com/pelletier/go-toml v1.2.0 // indirect github.com/prebid/go-gdpr v0.8.3 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed @@ -55,9 +53,7 @@ require ( github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect - golang.org/x/text v0.3.0 - gopkg.in/yaml.v2 v2.2.2 + golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb + golang.org/x/text v0.3.3 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index f9c9bb887da..7a38c2f1dd5 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,27 @@ github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5b github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -60,15 +75,19 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mxmCherry/openrtb/v14 v14.0.0 h1:7CUpdQi6Hqi9k03AaUAZVYF3Kqt6ZXryBUFQv8IA/N8= -github.com/mxmCherry/openrtb/v14 v14.0.0/go.mod h1:aAj4RuDpol+zMEVyKiDqiXPHfXevLVmyAf3f6BkRaJw= +github.com/mxmCherry/openrtb/v15 v15.0.0 h1:inLuQ3Bsima9HLB2v6WjbtEFF69SWOT5Dux4QZtYdrw= +github.com/mxmCherry/openrtb/v15 v15.0.0/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlVDbxf9Fyy1TyhZA= +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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= +github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -122,30 +141,63 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index 16b66fc20fc..29e62da3d35 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -1,6 +1,6 @@ package openrtb_ext -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" type BidRequestVideo struct { // Attribute: diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index 66236e9477d..8aeedb81a5e 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "encoding/json" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // DealTier defines the configuration of a deal tier. diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 607956ecb38..29d58d6c071 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index d61ec54a887..1c7177daf49 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -1,6 +1,6 @@ package openrtb_ext -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" // ExtBidResponse defines the contract for bidresponse.ext type ExtBidResponse struct { diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index 795cfaf9533..52840d95d9c 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/cache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 67b5681deca..41f1c39447b 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,6 +1,6 @@ package ccpa -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" // ConsentWriter implements the PolicyWriter interface for CCPA. type ConsentWriter struct { diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index d805936cf72..d59428626b8 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 183a774363c..33563b50567 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,7 +3,7 @@ package ccpa import ( "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 64250375106..d57ba8deaa4 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 31eca2cca57..416ebffa31a 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 4b946966206..ab2f64a691b 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -1,6 +1,6 @@ package privacy -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index 5080709f2cb..61899e4d60e 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -3,7 +3,7 @@ package privacy import ( "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 53885e51d49..ca784b7a5c1 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -3,7 +3,7 @@ package gdpr import ( "encoding/json" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" ) diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index 9b23dccc6e4..5753442fa01 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index 07bd7ce0a70..55e1764c8c2 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -3,7 +3,7 @@ package lmt import ( "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/util/iosutil" ) diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 5071adf4d04..2caec1be64c 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/util/iosutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index e115dc3802f..0f2829254a2 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -1,6 +1,6 @@ package lmt -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" const ( trackingUnrestricted = 0 diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index e36f30230d8..f475d2fb702 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -3,7 +3,7 @@ package lmt import ( "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/scrubber.go b/privacy/scrubber.go index d656168ae12..edaa5bb07c6 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 2d352e71821..9207315f593 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/writer.go b/privacy/writer.go index 610508f812c..0f04a52f292 100644 --- a/privacy/writer.go +++ b/privacy/writer.go @@ -1,6 +1,6 @@ package privacy -import "github.com/mxmCherry/openrtb/v14/openrtb2" +import "github.com/mxmCherry/openrtb/v15/openrtb2" // PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. type PolicyWriter interface { diff --git a/privacy/writer_test.go b/privacy/writer_test.go index b419e3c6783..f5b02387124 100644 --- a/privacy/writer_test.go +++ b/privacy/writer_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) From cc84f0234c3a30ed1599bb32d151074f81f9b50a Mon Sep 17 00:00:00 2001 From: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:51:27 -0400 Subject: [PATCH 376/603] IX: Set bidVideo when category and duration is available (#1794) --- adapters/ix/ix.go | 18 ++++++++-- adapters/ix/ix_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 38294cc587b..f8903008328 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -402,9 +402,23 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque if !ok { errs = append(errs, fmt.Errorf("Unmatched impression id: %s.", bid.ImpID)) } + + var bidExtVideo *openrtb_ext.ExtBidPrebidVideo + var bidExt openrtb_ext.ExtBid + if bidType == openrtb_ext.BidTypeVideo { + unmarshalExtErr := json.Unmarshal(bid.Ext, &bidExt) + if unmarshalExtErr == nil && bidExt.Prebid != nil && bidExt.Prebid.Video != nil { + bidExtVideo = &openrtb_ext.ExtBidPrebidVideo{ + Duration: bidExt.Prebid.Video.Duration, + PrimaryCategory: bidExt.Prebid.Video.PrimaryCategory, + } + } + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: bidType, + Bid: &bid, + BidType: bidType, + BidVideo: bidExtVideo, }) } } diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index 27d9370e1b7..bc70f3999df 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -722,3 +722,80 @@ func TestIxMaxRequests(t *testing.T) { t.Fatalf("Should have received %d bid", adapter.maxRequests) } } + +func TestIxMakeBidsWithCategoryDuration(t *testing.T) { + bidder := &IxAdapter{} + + mockedReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "1_1", + Video: &openrtb2.Video{ + W: 640, + H: 360, + MIMEs: []string{"video/mp4"}, + MaxDuration: 60, + Protocols: []openrtb2.Protocol{2, 3, 5, 6}, + }, + Ext: json.RawMessage( + `{ + "prebid": {}, + "bidder": { + "siteID": 123456 + } + }`, + )}, + }, + } + mockedExtReq := &adapters.RequestData{} + mockedBidResponse := &openrtb2.BidResponse{ + ID: "test-1", + SeatBid: []openrtb2.SeatBid{{ + Seat: "Buyer", + Bid: []openrtb2.Bid{{ + ID: "1", + ImpID: "1_1", + Price: 1.23, + AdID: "123", + Ext: json.RawMessage( + `{ + "prebid": { + "video": { + "duration": 60, + "primary_category": "IAB18-1" + } + } + }`, + ), + }}, + }}, + } + body, _ := json.Marshal(mockedBidResponse) + mockedRes := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + + expectedBidCount := 1 + expectedBidType := openrtb_ext.BidTypeVideo + expectedBidDuration := 60 + expectedBidCategory := "IAB18-1" + expectedErrorCount := 0 + + bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes) + + if len(bidResponse.Bids) != expectedBidCount { + t.Errorf("should have 1 bid, bids=%v", bidResponse.Bids) + } + if bidResponse.Bids[0].BidType != expectedBidType { + t.Errorf("bid type should be video, bidType=%s", bidResponse.Bids[0].BidType) + } + if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration { + t.Errorf("video duration should be set") + } + if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory { + t.Errorf("video category should be set") + } + if len(errors) != expectedErrorCount { + t.Errorf("should not have any errors, errors=%v", errors) + } +} From 22191e6c2ca3ea2c1acf20fe2d4cabe1eb104d54 Mon Sep 17 00:00:00 2001 From: Michael Burns Date: Wed, 14 Apr 2021 12:02:05 -0400 Subject: [PATCH 377/603] Update IX defaults (#1799) Co-authored-by: Mike Burns --- config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index b300bc58e49..18a8c29d9a9 100644 --- a/config/config.go +++ b/config/config.go @@ -606,7 +606,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") - // openrtb_ext.BidderIx doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") // openrtb_ext.BidderInvibes doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D") @@ -855,7 +855,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") - v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919") + v.SetDefault("adapters.ix.disabled", true) v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}") v.SetDefault("adapters.invibes.endpoint", "https://{{.Host}}/bid/ServerBidAdContent") From bdfbc32ca73a71d77d9ce6f91cde87efd71491d3 Mon Sep 17 00:00:00 2001 From: guiann Date: Wed, 14 Apr 2021 19:19:36 +0200 Subject: [PATCH 378/603] Update Adyoulike endpoint to hit production servers (#1805) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 18a8c29d9a9..8d53a4668f1 100644 --- a/config/config.go +++ b/config/config.go @@ -819,7 +819,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") - v.SetDefault("adapters.adyoulike.endpoint", "https://broker-preprod.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") + v.SetDefault("adapters.adyoulike.endpoint", "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") From 5bec86b74bcb7b2214ec453cc01d8b38913f2b6e Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Wed, 14 Apr 2021 12:43:26 -0700 Subject: [PATCH 379/603] Openx: use bidfloor if set - prebid.js adapter behavior (#1795) --- adapters/openx/openx.go | 4 +++- .../openxtest/exemplary/optional-params.json | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index d53d405ba24..208b06f7c86 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -130,7 +130,9 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error { reqExt.Platform = openxExt.Platform imp.TagID = openxExt.Unit - imp.BidFloor = openxExt.CustomFloor + if imp.BidFloor == 0 && openxExt.CustomFloor > 0 { + imp.BidFloor = openxExt.CustomFloor + } imp.Ext = nil if openxExt.CustomParams != nil { diff --git a/adapters/openx/openxtest/exemplary/optional-params.json b/adapters/openx/openxtest/exemplary/optional-params.json index b2fd9c2f4fb..93dbafc5bfb 100644 --- a/adapters/openx/openxtest/exemplary/optional-params.json +++ b/adapters/openx/openxtest/exemplary/optional-params.json @@ -16,6 +16,21 @@ "customParams": {"foo": "bar"} } } + }, + { + "bidfloor": 0.5, + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "unit": "539439964", + "delDomain": "se-demo-d.openx.net", + "platform": "PLATFORM", + "customFloor": 0.1 + } + } } ] }, @@ -37,6 +52,14 @@ "ext": { "customParams": {"foo": "bar"} } + }, + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "tagid": "539439964", + "bidfloor": 0.5 } ], "ext": { From 398e1ca3c311d90f0da7a142850659a6ca296df7 Mon Sep 17 00:00:00 2001 From: Arne Schulz Date: Wed, 14 Apr 2021 22:55:42 +0200 Subject: [PATCH 380/603] [ORBIDDER] add gvlVendorID and set bid response currency (#1798) --- adapters/orbidder/orbidder.go | 4 +++- static/bidder-info/orbidder.yaml | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index 5e8ea3db84f..77985c8dae0 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -110,7 +110,6 @@ func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb2.BidRequest, extern } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - for _, seatBid := range bidResp.SeatBid { for _, bid := range seatBid.Bid { bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ @@ -119,6 +118,9 @@ func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb2.BidRequest, extern }) } } + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } return bidResponse, nil } diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index c683087d197..467093c1256 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -1,9 +1,7 @@ maintainer: email: "realtime-siggi@otto.de" +gvlVendorID: 559 capabilities: app: mediaTypes: - banner - site: - mediaTypes: - - banner \ No newline at end of file From 1628e1af0ffb64b7f2545e57f55103b678422e5c Mon Sep 17 00:00:00 2001 From: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Date: Thu, 15 Apr 2021 02:45:07 +0000 Subject: [PATCH 381/603] New Adapter: ADXCG (#1803) --- adapters/adxcg/adxcg.go | 125 ++++++++++++++++++ adapters/adxcg/adxcg_test.go | 23 ++++ .../adxcgtest/exemplary/simple-banner.json | 83 ++++++++++++ .../adxcgtest/exemplary/simple-native.json | 73 ++++++++++ .../adxcgtest/exemplary/simple-video.json | 105 +++++++++++++++ .../adxcg/adxcgtest/params/race/banner.json | 1 + .../adxcgtest/supplemental/bad_response.json | 61 +++++++++ .../adxcgtest/supplemental/status_204.json | 56 ++++++++ .../adxcgtest/supplemental/status_400.json | 61 +++++++++ .../adxcgtest/supplemental/status_418.json | 61 +++++++++ adapters/adxcg/usersync.go | 12 ++ adapters/adxcg/usersync_test.go | 29 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/adxcg.yaml | 13 ++ static/bidder-params/adxcg.json | 14 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 19 files changed, 726 insertions(+) create mode 100644 adapters/adxcg/adxcg.go create mode 100644 adapters/adxcg/adxcg_test.go create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-banner.json create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-native.json create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-video.json create mode 100644 adapters/adxcg/adxcgtest/params/race/banner.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/bad_response.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_204.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_400.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_418.json create mode 100644 adapters/adxcg/usersync.go create mode 100644 adapters/adxcg/usersync_test.go create mode 100644 static/bidder-info/adxcg.yaml create mode 100644 static/bidder-params/adxcg.json diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go new file mode 100644 index 00000000000..e9f6c94bc40 --- /dev/null +++ b/adapters/adxcg/adxcg.go @@ -0,0 +1,125 @@ +package adxcg + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +type adapter struct { + endpoint string +} + +// MakeRequests prepares the HTTP requests which should be made to fetch bids. +func (adapter *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + openRTBRequestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info" + +// MakeBids unpacks the server's response into Bids. +func (adapter *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + switch bidderRawResponse.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid) + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + var typedBid *adapters.TypedBid + for _, seatBid := range openRTBBidderResponse.SeatBid { + for _, bid := range seatBid.Bid { + activeBid := bid + bidType, err := getMediaTypeForImp(activeBid.ImpID, openRTBRequest.Imp) + if err != nil { + errs = append(errs, err) + continue + } + + typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } + } + + return bidderResponse, nil + +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), + } +} diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go new file mode 100644 index 00000000000..d01e62f670c --- /dev/null +++ b/adapters/adxcg/adxcg_test.go @@ -0,0 +1,23 @@ +package adxcg + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsDir = "adxcgtest" +const testsBidderEndpoint = "http://localhost/prebid_server" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdxcg, config.Adapter{ + Endpoint: testsBidderEndpoint}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..85ed18ff1a3 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "adxcg", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-native.json b/adapters/adxcg/adxcgtest/exemplary/simple-native.json new file mode 100644 index 00000000000..1a449e601a2 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-native.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-native-id", + "imp": [ + { + "id": "test-imp-native-id", + "native": { + "request": "test-native", + "ver": "1.2" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-native-id", + "imp": [ + { + "id": "test-imp-native-id", + "native": { + "request": "test-native", + "ver": "1.2" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adxcg", + "bid": [ + { + "id": "test-request-native-id", + "impid": "test-imp-native-id", + "price": 1.16, + "adm": "native-ad", + "w": 1, + "h": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-native-id", + "impid": "test-imp-native-id", + "price": 1.16, + "adm": "native-ad", + "w": 1, + "h": 1 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-video.json b/adapters/adxcg/adxcgtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b5f54b9bc44 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-video.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-video-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [ + 1, + 2, + 5, + 6, + 7 + ], + "mimes": [ + "video\/mp4" + ], + "placement": 4, + "protocols": [ + 2 + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-video-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [ + 1, + 2, + 5, + 6, + 7 + ], + "mimes": [ + "video\/mp4" + ], + "placement": 4, + "protocols": [ + 2 + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adxcg", + "bid": [ + { + "id": "test-request-video-id", + "impid": "test-imp-video-id", + "price": 1.16, + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-video-id", + "impid": "test-imp-video-id", + "price": 1.16, + "adm": "some-test-ad", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adxcg/adxcgtest/params/race/banner.json b/adapters/adxcg/adxcgtest/params/race/banner.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/adapters/adxcg/adxcgtest/params/race/banner.json @@ -0,0 +1 @@ +{} diff --git a/adapters/adxcg/adxcgtest/supplemental/bad_response.json b/adapters/adxcg/adxcgtest/supplemental/bad_response.json new file mode 100644 index 00000000000..f84f5555259 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/bad_response.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adxcg/adxcgtest/supplemental/status_204.json b/adapters/adxcg/adxcgtest/supplemental/status_204.json new file mode 100644 index 00000000000..0702c103332 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_204.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/adxcg/adxcgtest/supplemental/status_400.json b/adapters/adxcg/adxcgtest/supplemental/status_400.json new file mode 100644 index 00000000000..65d21406bf0 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_400.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adxcg/adxcgtest/supplemental/status_418.json b/adapters/adxcg/adxcgtest/supplemental/status_418.json new file mode 100644 index 00000000000..4c5dd576aa6 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_418.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adxcg/usersync.go b/adapters/adxcg/usersync.go new file mode 100644 index 00000000000..c6627059703 --- /dev/null +++ b/adapters/adxcg/usersync.go @@ -0,0 +1,12 @@ +package adxcg + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdxcgSyncer(urlTemplate *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adxcg", urlTemplate, adapters.SyncTypeRedirect) +} diff --git a/adapters/adxcg/usersync_test.go b/adapters/adxcg/usersync_test.go new file mode 100644 index 00000000000..53a757f2732 --- /dev/null +++ b/adapters/adxcg/usersync_test.go @@ -0,0 +1,29 @@ +package adxcg + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdxcgSyncer(t *testing.T) { + syncURL := "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdxcgSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 8d53a4668f1..14a801aef88 100644 --- a/config/config.go +++ b/config/config.go @@ -579,6 +579,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + // openrtb_ext.BidderAdxcg doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdyoulike, "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadyoulike%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BBUYER_USERID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAMX, "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Damx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") @@ -819,6 +820,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.adxcg.disabled", true) v.SetDefault("adapters.adyoulike.endpoint", "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 307a07fafd7..51e5b4cd0fe 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adxcg" "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" "github.com/prebid/prebid-server/adapters/amx" @@ -130,6 +131,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdxcg: adxcg.Builder, openrtb_ext.BidderAdyoulike: adyoulike.Builder, openrtb_ext.BidderAJA: aja.Builder, openrtb_ext.BidderAMX: amx.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f48cb23047a..e600eb05b82 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -90,6 +90,7 @@ const ( BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" + BidderAdxcg BidderName = "adxcg" BidderAdyoulike BidderName = "adyoulike" BidderAJA BidderName = "aja" BidderAMX BidderName = "amx" @@ -201,6 +202,7 @@ func CoreBidderNames() []BidderName { BidderAdtarget, BidderAdtelligent, BidderAdvangelists, + BidderAdxcg, BidderAdyoulike, BidderAJA, BidderAMX, diff --git a/static/bidder-info/adxcg.yaml b/static/bidder-info/adxcg.yaml new file mode 100644 index 00000000000..e5535ae7258 --- /dev/null +++ b/static/bidder-info/adxcg.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "info@adxcg.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/adxcg.json b/static/bidder-params/adxcg.json new file mode 100644 index 00000000000..3f89234359d --- /dev/null +++ b/static/bidder-params/adxcg.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adxcg Adapter Params", + "description": "A schema which validates params accepted by the Adxcg adapter", + "type": "object", + "properties": { + "adzoneid": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the placement selling the impression" + } + }, + "required": ["adzoneid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 104312cc164..50362ad04ec 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -17,6 +17,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adxcg" "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" "github.com/prebid/prebid-server/adapters/amx" @@ -112,6 +113,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtarget, adtarget.NewAdtargetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdxcg, adxcg.NewAdxcgSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdyoulike, adyoulike.NewAdyoulikeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAMX, amx.NewAMXSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index e0dc3dd2896..7327630b464 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdtarget): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, + string(openrtb_ext.BidderAdxcg): syncConfig, string(openrtb_ext.BidderAdyoulike): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, string(openrtb_ext.BidderAMX): syncConfig, From b3b6099e68872560f9967efefd95b4ba1fba1f69 Mon Sep 17 00:00:00 2001 From: agilfix Date: Thu, 15 Apr 2021 09:33:29 -0400 Subject: [PATCH 382/603] Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. --- static/bidder-params/kidoz.json | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/static/bidder-params/kidoz.json b/static/bidder-params/kidoz.json index 79e2edc2fd2..16511a15ca7 100644 --- a/static/bidder-params/kidoz.json +++ b/static/bidder-params/kidoz.json @@ -5,22 +5,18 @@ "type": "object", "properties": { "access_token": { - "$ref": "#/definitions/non-empty-string", + "type": "string", + "minLength": 1, "description": "Kidoz access_token" }, "publisher_id": { - "$ref": "#/definitions/non-empty-string", - "description": "Kidoz publisher_id" - } - }, - "definitions": { - "non-empty-string": { "type": "string", - "minLength": 1 + "minLength": 1, + "description": "Kidoz publisher_id" } }, "required": [ "access_token", "publisher_id" ] -} \ No newline at end of file +} From 51822e1daa9485f9d3c66449faec6474882da688 Mon Sep 17 00:00:00 2001 From: agilfix Date: Thu, 15 Apr 2021 11:04:38 -0400 Subject: [PATCH 383/603] Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. --- static/bidder-params/bidmachine.json | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/static/bidder-params/bidmachine.json b/static/bidder-params/bidmachine.json index f3971c00274..819dc9b7e2d 100644 --- a/static/bidder-params/bidmachine.json +++ b/static/bidder-params/bidmachine.json @@ -5,22 +5,19 @@ "type": "object", "properties": { "host": { - "$ref": "#/definitions/non-empty-string", + "type": "string", + "minLength": 1, "description": "Host" }, "path": { - "$ref": "#/definitions/non-empty-string", + "type": "string", + "minLength": 1, "description": "URL path" }, "seller_id": { - "$ref": "#/definitions/non-empty-string", - "description": "Seller Identifier" - } - }, - "definitions": { - "non-empty-string": { "type": "string", - "minLength": 1 + "minLength": 1, + "description": "Seller Identifier" } }, "required": [ @@ -28,4 +25,4 @@ "path", "seller_id" ] -} \ No newline at end of file +} From c7b64639110ad7d6699706b5f9c016f362f76d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Fri, 16 Apr 2021 21:50:56 +0200 Subject: [PATCH 384/603] transform native eventtrackers to imptrackers and jstracker (#1811) --- adapters/outbrain/outbrain.go | 33 +++++++++++++++++++ .../outbraintest/exemplary/native.json | 12 +++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index 02a9784303c..282a6d53aa0 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" + "github.com/mxmCherry/openrtb/v15/native1" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -118,6 +120,20 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R errs = append(errs, err) continue } + if bidType == openrtb_ext.BidTypeNative { + var nativePayload nativeResponse.Response + if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativePayload); err != nil { + errs = append(errs, err) + continue + } + transformEventTrackers(&nativePayload) + nativePayloadJson, err := json.Marshal(nativePayload) + if err != nil { + errs = append(errs, err) + continue + } + bid.AdM = string(nativePayloadJson) + } b := &adapters.TypedBid{ Bid: &bid, @@ -145,3 +161,20 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, Message: fmt.Sprintf("Failed to find native/banner impression \"%s\" ", impID), } } + +func transformEventTrackers(nativePayload *nativeResponse.Response) { + // the native-trk.js library used to trigger the trackers currently doesn't support the native 1.2 eventtrackers, + // so transform them to the deprecated imptrackers and jstracker + for _, eventTracker := range nativePayload.EventTrackers { + if eventTracker.Event != native1.EventTypeImpression { + continue + } + switch eventTracker.Method { + case native1.EventTrackingMethodImage: + nativePayload.ImpTrackers = append(nativePayload.ImpTrackers, eventTracker.URL) + case native1.EventTrackingMethodJS: + nativePayload.JSTracker = fmt.Sprintf("", eventTracker.URL) + } + } + nativePayload.EventTrackers = nil +} diff --git a/adapters/outbrain/outbraintest/exemplary/native.json b/adapters/outbrain/outbraintest/exemplary/native.json index d32fd60382e..bf1eb7f53ff 100644 --- a/adapters/outbrain/outbraintest/exemplary/native.json +++ b/adapters/outbrain/outbraintest/exemplary/native.json @@ -73,7 +73,7 @@ "impid": "test-imp-id", "price": 1000, "nurl": "http://example.com/win/1000", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"},{\"event\":1,\"method\":2,\"url\":\"http://example.com/impression\"}]}", "adomain": [ "example.com" ], @@ -81,9 +81,7 @@ "crid": "test-crid", "cat": [ "IAB13-4" - ], - "w": 300, - "h": 250 + ] } ], "seat": "acc-1876" @@ -105,7 +103,7 @@ "impid": "test-imp-id", "price": 1000, "nurl": "http://example.com/win/1000", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"imptrackers\":[\"http://example.com/impression\"],\"jstracker\":\"\\u003cscript src=\\\"http://example.com/impression\\\"\\u003e\\u003c/script\\u003e\"}", "adomain": [ "example.com" ], @@ -113,9 +111,7 @@ "crid": "test-crid", "cat": [ "IAB13-4" - ], - "w": 300, - "h": 250 + ] }, "type": "native" } From d384e91bf99f886927a2bd412fd439502a1de088 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Tue, 20 Apr 2021 19:45:21 +0300 Subject: [PATCH 385/603] TheMediaGrid: Added processing of imp[].ext.data (#1807) --- adapters/grid/grid.go | 34 +++++- .../grid/gridtest/exemplary/with_gpid.json | 100 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 adapters/grid/gridtest/exemplary/with_gpid.json diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index 2c2b291bab8..bc819fc067e 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -16,6 +16,23 @@ type GridAdapter struct { endpoint string } +type ExtImpDataAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + +type ExtImpData struct { + PbAdslot string `json:"pbadslot,omitempty"` + AdServer *ExtImpDataAdServer `json:"adserver,omitempty"` +} + +type ExtImp struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder"` + Data *ExtImpData `json:"data,omitempty"` + Gpid string `json:"gpid,omitempty"` +} + func processImp(imp *openrtb2.Imp) error { // get the grid extension var ext adapters.ExtImpBidder @@ -37,6 +54,21 @@ func processImp(imp *openrtb2.Imp) error { return nil } +func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { + var ext ExtImp + if err := json.Unmarshal(imp.Ext, &ext); err != nil { + return imp + } + if ext.Data != nil && ext.Data.AdServer != nil && ext.Data.AdServer.AdSlot != "" { + ext.Gpid = ext.Data.AdServer.AdSlot + extJSON, err := json.Marshal(ext) + if err == nil { + imp.Ext = extJSON + } + } + return imp +} + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) @@ -48,7 +80,7 @@ func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte // pre-process the imps for _, imp := range requestCopy.Imp { if err := processImp(&imp); err == nil { - validImps = append(validImps, imp) + validImps = append(validImps, setImpExtData(imp)) } else { errors = append(errors, err) } diff --git a/adapters/grid/gridtest/exemplary/with_gpid.json b/adapters/grid/gridtest/exemplary/with_gpid.json new file mode 100644 index 00000000000..f54013581de --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with_gpid.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + }, + "data": { + "adserver": { + "name": "some_name", + "adslot": "some_slot" + } + }, + "gpid": "some_slot" + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} From 89fc6483b99cdb112ca7618a120f5c46154780a1 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Fri, 23 Apr 2021 19:59:53 +0300 Subject: [PATCH 386/603] New Adapter: adf (adformOpenRTB) (#1815) * initial adformOpenRTB adapter implementation * do not make request copy * rename adfromOpenRTB adapter to adf * fix user sync url --- adapters/adf/adf.go | 106 +++++++++++++++++ adapters/adf/adf_test.go | 20 ++++ .../adf/adftest/exemplary/multi-native.json | 108 ++++++++++++++++++ .../adf/adftest/exemplary/single-native.json | 90 +++++++++++++++ adapters/adf/adftest/params/race/native.json | 3 + .../adf/adftest/supplemental/bad-request.json | 48 ++++++++ .../adftest/supplemental/empty-response.json | 42 +++++++ .../adftest/supplemental/nobid-response.json | 49 ++++++++ .../adftest/supplemental/server-error.json | 49 ++++++++ .../supplemental/unparsable-response.json | 49 ++++++++ adapters/adf/params_test.go | 57 +++++++++ adapters/adf/usersync.go | 12 ++ adapters/adf/usersync_test.go | 31 +++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adf.go | 9 ++ static/bidder-info/adf.yaml | 10 ++ static/bidder-params/adf.json | 14 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 21 files changed, 706 insertions(+) create mode 100644 adapters/adf/adf.go create mode 100644 adapters/adf/adf_test.go create mode 100644 adapters/adf/adftest/exemplary/multi-native.json create mode 100644 adapters/adf/adftest/exemplary/single-native.json create mode 100644 adapters/adf/adftest/params/race/native.json create mode 100644 adapters/adf/adftest/supplemental/bad-request.json create mode 100644 adapters/adf/adftest/supplemental/empty-response.json create mode 100644 adapters/adf/adftest/supplemental/nobid-response.json create mode 100644 adapters/adf/adftest/supplemental/server-error.json create mode 100644 adapters/adf/adftest/supplemental/unparsable-response.json create mode 100644 adapters/adf/params_test.go create mode 100644 adapters/adf/usersync.go create mode 100644 adapters/adf/usersync_test.go create mode 100644 openrtb_ext/imp_adf.go create mode 100644 static/bidder-info/adf.yaml create mode 100644 static/bidder-params/adf.json diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go new file mode 100644 index 00000000000..4ed0dc077dc --- /dev/null +++ b/adapters/adf/adf.go @@ -0,0 +1,106 @@ +package adf + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Adf adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + var validImps = make([]openrtb2.Imp, 0, len(request.Imp)) + + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var adfImpExt openrtb_ext.ExtImpAdf + if err := json.Unmarshal(bidderExt.Bidder, &adfImpExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = adfImpExt.MasterTagID.String() + validImps = append(validImps, imp) + } + + request.Imp = validImps + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeNative, + }) + } + } + + return bidResponse, nil +} diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go new file mode 100644 index 00000000000..ab348db36ae --- /dev/null +++ b/adapters/adf/adf_test.go @@ -0,0 +1,20 @@ +package adf + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdf, config.Adapter{ + Endpoint: "https://adx.adform.net/adx/openrtb"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adftest", bidder) +} diff --git a/adapters/adf/adftest/exemplary/multi-native.json b/adapters/adf/adftest/exemplary/multi-native.json new file mode 100644 index 00000000000..40bd7e15773 --- /dev/null +++ b/adapters/adf/adftest/exemplary/multi-native.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + }, + "tagid": "828782" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + }, + "tagid": "828783" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "adomain": [], + "crid": "test-creative-id-1" + }, { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "crid": "test-creative-id-1" + }, + "type": "native" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }, + "type": "native" + }] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-native.json b/adapters/adf/adftest/exemplary/single-native.json new file mode 100644 index 00000000000..909f8cec9a7 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-native.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "adomain": [], + "crid": "test-creative-id" + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id" + }, + "type": "native" + } + ] + }] +} diff --git a/adapters/adf/adftest/params/race/native.json b/adapters/adf/adftest/params/race/native.json new file mode 100644 index 00000000000..79535d85da4 --- /dev/null +++ b/adapters/adf/adftest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "mid": "828782" +} diff --git a/adapters/adf/adftest/supplemental/bad-request.json b/adapters/adf/adftest/supplemental/bad-request.json new file mode 100644 index 00000000000..7424eae4656 --- /dev/null +++ b/adapters/adf/adftest/supplemental/bad-request.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/adftest/supplemental/empty-response.json b/adapters/adf/adftest/supplemental/empty-response.json new file mode 100644 index 00000000000..b2d6eab97fe --- /dev/null +++ b/adapters/adf/adftest/supplemental/empty-response.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adf/adftest/supplemental/nobid-response.json b/adapters/adf/adftest/supplemental/nobid-response.json new file mode 100644 index 00000000000..ec559aa7468 --- /dev/null +++ b/adapters/adf/adftest/supplemental/nobid-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": null, + "bidid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/adf/adftest/supplemental/server-error.json b/adapters/adf/adftest/supplemental/server-error.json new file mode 100644 index 00000000000..15604ad2189 --- /dev/null +++ b/adapters/adf/adftest/supplemental/server-error.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 500, + "body": "Server error" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/adftest/supplemental/unparsable-response.json b/adapters/adf/adftest/supplemental/unparsable-response.json new file mode 100644 index 00000000000..091f05cea22 --- /dev/null +++ b/adapters/adf/adftest/supplemental/unparsable-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go new file mode 100644 index 00000000000..779d3fb6f2d --- /dev/null +++ b/adapters/adf/params_test.go @@ -0,0 +1,57 @@ +package adf + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adf.json +// +// These also validate the format of the external API: request.imp[i].ext.adf + +// TestValidParams makes sure that the adform schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdf, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adform params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adform schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdf, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"mid":123}`, + `{"mid":"123"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"notmid":"123"}`, + `{"mid":"placementID"}`, +} diff --git a/adapters/adf/usersync.go b/adapters/adf/usersync.go new file mode 100644 index 00000000000..e3bd11422c4 --- /dev/null +++ b/adapters/adf/usersync.go @@ -0,0 +1,12 @@ +package adf + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdfSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adf", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/adf/usersync_test.go b/adapters/adf/usersync_test.go new file mode 100644 index 00000000000..693e6418444 --- /dev/null +++ b/adapters/adf/usersync_test.go @@ -0,0 +1,31 @@ +package adf + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" +) + +func TestAdfSyncer(t *testing.T) { + syncURL := "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdfSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 14a801aef88..b18fa3c5691 100644 --- a/config/config.go +++ b/config/config.go @@ -568,6 +568,7 @@ func (cfg *Configuration) setDerivedDefaults() { externalURL := cfg.ExternalURL setDefaultUsersync(cfg.Adapters, openrtb_ext.Bidder33Across, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAcuityAds, "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdf, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdform, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderAdgeneration doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") @@ -805,6 +806,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") + v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb") v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 51e5b4cd0fe..8fda6a28b6e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -4,6 +4,7 @@ import ( "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adgeneration" "github.com/prebid/prebid-server/adapters/adhese" @@ -116,6 +117,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { return map[openrtb_ext.BidderName]adapters.Builder{ openrtb_ext.Bidder33Across: ttx.Builder, openrtb_ext.BidderAcuityAds: acuityads.Builder, + openrtb_ext.BidderAdf: adf.Builder, openrtb_ext.BidderAdform: adform.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, openrtb_ext.BidderAdhese: adhese.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index e600eb05b82..77f14bbc40e 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -75,6 +75,7 @@ func IsBidderNameReserved(name string) bool { const ( Bidder33Across BidderName = "33across" BidderAcuityAds BidderName = "acuityads" + BidderAdf BidderName = "adf" BidderAdform BidderName = "adform" BidderAdgeneration BidderName = "adgeneration" BidderAdhese BidderName = "adhese" @@ -187,6 +188,7 @@ func CoreBidderNames() []BidderName { return []BidderName{ Bidder33Across, BidderAcuityAds, + BidderAdf, BidderAdform, BidderAdgeneration, BidderAdhese, diff --git a/openrtb_ext/imp_adf.go b/openrtb_ext/imp_adf.go new file mode 100644 index 00000000000..8ef02a460b2 --- /dev/null +++ b/openrtb_ext/imp_adf.go @@ -0,0 +1,9 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpAdf struct { + MasterTagID json.Number `json:"mid"` +} diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml new file mode 100644 index 00000000000..8cc96350703 --- /dev/null +++ b/static/bidder-info/adf.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "scope.sspp@adform.com" +gvlVendorID: 50 +capabilities: + app: + mediaTypes: + - native + site: + mediaTypes: + - native diff --git a/static/bidder-params/adf.json b/static/bidder-params/adf.json new file mode 100644 index 00000000000..a16df36d681 --- /dev/null +++ b/static/bidder-params/adf.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adf Adapter Params", + "description": "A schema which validates params accepted by the adf adapter", + "type": "object", + "properties": { + "mid": { + "type": ["integer", "string"], + "pattern": "^\\d+$", + "description": "An ID which identifies the placement selling the impression" + } + }, + "required": ["mid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 50362ad04ec..3dc93c3bfc8 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -7,6 +7,7 @@ import ( "github.com/golang/glog" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" @@ -103,6 +104,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.Bidder33Across, ttx.New33AcrossSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAcuityAds, acuityads.NewAcuityAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdf, adf.NewAdfSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7327630b464..6d46a4a4e4d 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -16,6 +16,7 @@ func TestNewSyncerMap(t *testing.T) { Adapters: map[string]config.Adapter{ string(openrtb_ext.Bidder33Across): syncConfig, string(openrtb_ext.BidderAcuityAds): syncConfig, + string(openrtb_ext.BidderAdf): syncConfig, string(openrtb_ext.BidderAdform): syncConfig, string(openrtb_ext.BidderAdkernel): syncConfig, string(openrtb_ext.BidderAdkernelAdn): syncConfig, From 2c16dcc9d71d1ef0661cb0d0fbe87920ef864952 Mon Sep 17 00:00:00 2001 From: mefjush Date: Mon, 26 Apr 2021 15:11:34 +0200 Subject: [PATCH 387/603] Set Adhese gvl id and vast modification flag (#1821) --- static/bidder-info/adhese.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/bidder-info/adhese.yaml b/static/bidder-info/adhese.yaml index 742d78344ce..25058e82409 100644 --- a/static/bidder-info/adhese.yaml +++ b/static/bidder-info/adhese.yaml @@ -1,5 +1,7 @@ maintainer: email: info@adhese.com +gvlVendorID: 553 +modifyingVastXmlAllowed: true capabilities: app: mediaTypes: @@ -8,4 +10,4 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video From 7739f10b7d2812daf0d7eab8254eb63a71efb34b Mon Sep 17 00:00:00 2001 From: dtbarne <7635750+dtbarne@users.noreply.github.com> Date: Mon, 26 Apr 2021 08:12:41 -0500 Subject: [PATCH 388/603] Added gvlVendorID for mobilefuse (#1822) --- static/bidder-info/mobilefuse.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml index 178e407d927..18a90a8866e 100644 --- a/static/bidder-info/mobilefuse.yaml +++ b/static/bidder-info/mobilefuse.yaml @@ -1,5 +1,6 @@ maintainer: email: prebid@mobilefuse.com +gvlVendorID: 909 capabilities: app: mediaTypes: From 6be1b084b6a6b94ab2cd486be0dbe3393db96aac Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Mon, 26 Apr 2021 10:41:02 -0400 Subject: [PATCH 389/603] AppNexus: reform bid floor handling (#1814) --- adapters/appnexus/appnexus.go | 2 +- .../supplemental/reserve-ignored.json | 144 ++++++++++++++++++ .../supplemental/reserve-test.json | 143 +++++++++++++++++ 3 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 adapters/appnexus/appnexustest/supplemental/reserve-ignored.json create mode 100644 adapters/appnexus/appnexustest/supplemental/reserve-test.json diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 046f5d312d7..4d4324c44de 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -495,7 +495,7 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, err if appnexusExt.InvCode != "" { imp.TagID = appnexusExt.InvCode } - if appnexusExt.Reserve > 0 { + if imp.BidFloor <= 0 && appnexusExt.Reserve > 0 { imp.BidFloor = appnexusExt.Reserve // This will be broken for non-USD currency. } if imp.Banner != nil { diff --git a/adapters/appnexus/appnexustest/supplemental/reserve-ignored.json b/adapters/appnexus/appnexustest/supplemental/reserve-ignored.json new file mode 100644 index 00000000000..84e2b48484e --- /dev/null +++ b/adapters/appnexus/appnexustest/supplemental/reserve-ignored.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 0.3, + "ext": { + "bidder": { + "placement_id": 1, + "reserve": 0.4 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ib.adnxs.com/openrtb2", + "body": { + "id": "test-request-id", + "ext": { + "appnexus": { + "hb_source": 5 + }, + "prebid": {} + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "bidfloor": 0.3, + "ext": { + "appnexus": { + "placement_id": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000, + "deal_priority": 5 + } + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": ["IAB20-3"], + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000, + "deal_priority": 5 + } + } + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/appnexus/appnexustest/supplemental/reserve-test.json b/adapters/appnexus/appnexustest/supplemental/reserve-test.json new file mode 100644 index 00000000000..f5c6dfceb4f --- /dev/null +++ b/adapters/appnexus/appnexustest/supplemental/reserve-test.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placement_id": 1, + "reserve": 0.4 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ib.adnxs.com/openrtb2", + "body": { + "id": "test-request-id", + "ext": { + "appnexus": { + "hb_source": 5 + }, + "prebid": {} + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "bidfloor": 0.4, + "ext": { + "appnexus": { + "placement_id": 1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000, + "deal_priority": 5 + } + } + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["appnexus.com"], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "cat": ["IAB20-3"], + "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 8189378542222915032, + "bid_ad_type": 0, + "bidder_id": 2, + "ranking_price": 0.000000, + "deal_priority": 5 + } + } + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file From b9bac73ff673de323ffb8c4f89e746bc8f498166 Mon Sep 17 00:00:00 2001 From: Pillsoo Shin Date: Tue, 27 Apr 2021 03:53:28 +0900 Subject: [PATCH 390/603] PubNative: Add GVL Vendor ID (#1824) --- static/bidder-info/pubnative.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/pubnative.yaml b/static/bidder-info/pubnative.yaml index 65bc2659dfe..44bba23d7f3 100644 --- a/static/bidder-info/pubnative.yaml +++ b/static/bidder-info/pubnative.yaml @@ -1,5 +1,6 @@ maintainer: email: product@pubnative.net +gvlVendorID: 512 capabilities: app: mediaTypes: From 12d0dacb2d64c8aeb4c86693138b3d080dad80d7 Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Tue, 27 Apr 2021 09:17:34 -0700 Subject: [PATCH 391/603] InMobi: adding gvlVendorID to static yaml (#1826) --- static/bidder-info/inmobi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 3f8cdd8cb91..9014783833f 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,6 +1,6 @@ maintainer: email: "prebid-support@inmobi.com" - +gvlVendorID: 333 capabilities: app: mediaTypes: From 700b4adf074b7298a056a6d3f1148b2096dae892 Mon Sep 17 00:00:00 2001 From: epomrnd Date: Tue, 27 Apr 2021 19:18:14 +0300 Subject: [PATCH 392/603] Epom Adapter: configure vendor id (GVL ID) (#1828) Co-authored-by: Vasyl Zarva --- static/bidder-info/epom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/epom.yaml b/static/bidder-info/epom.yaml index 32afa346c9e..991944ea35f 100644 --- a/static/bidder-info/epom.yaml +++ b/static/bidder-info/epom.yaml @@ -1,6 +1,7 @@ maintainer: email: "support@epom.com" modifyingVastXmlAllowed: true +gvlVendorID: 849 capabilities: app: mediaTypes: From cbab6bd427241db5c49d075c57d02f09ccb0532f Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 27 Apr 2021 20:13:01 +0300 Subject: [PATCH 393/603] Update Adtarget gvlid (#1829) --- static/bidder-info/adtarget.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml index d52f18ac697..cc064b9ca6b 100644 --- a/static/bidder-info/adtarget.yaml +++ b/static/bidder-info/adtarget.yaml @@ -1,5 +1,6 @@ maintainer: email: "kamil@adtarget.com.tr" +gvlVendorID: 779 capabilities: app: mediaTypes: From 8547a9896863c4d7f8b0722da232bf4ad8e3512b Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Wed, 28 Apr 2021 12:39:06 -0700 Subject: [PATCH 394/603] Adding site to static yaml, and exemplary tests (#1827) --- ...ple-banner.json => simple-app-banner.json} | 0 ...imple-video.json => simple-app-video.json} | 0 .../exemplary/simple-web-banner.json | 105 +++++++++++++++++ .../exemplary/simple-web-video.json | 109 ++++++++++++++++++ static/bidder-info/inmobi.yaml | 4 + 5 files changed, 218 insertions(+) rename adapters/inmobi/inmobitest/exemplary/{simple-banner.json => simple-app-banner.json} (100%) rename adapters/inmobi/inmobitest/exemplary/{simple-video.json => simple-app-video.json} (100%) create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-banner.json create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-video.json diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json similarity index 100% rename from adapters/inmobi/inmobitest/exemplary/simple-banner.json rename to adapters/inmobi/inmobitest/exemplary/simple-app-banner.json diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json similarity index 100% rename from adapters/inmobi/inmobitest/exemplary/simple-video.json rename to adapters/inmobi/inmobitest/exemplary/simple-app-video.json diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..131249ba8a1 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1617941157285" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1617941157285" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json new file mode 100644 index 00000000000..3aed605f416 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": " ", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": " ", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "video" + }] + }] +} + + diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 9014783833f..9b11640f262 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -6,3 +6,7 @@ capabilities: mediaTypes: - banner - video + site: + mediaTypes: + - banner + - video From 3e08875ff230b328a8da6d0e940ea875a679d11c Mon Sep 17 00:00:00 2001 From: Marcin Muras <47107445+mmuras@users.noreply.github.com> Date: Thu, 29 Apr 2021 18:38:20 +0200 Subject: [PATCH 395/603] AdOcean adapter - add support for mobile apps (#1830) --- adapters/adocean/adocean.go | 62 ++++++++++------ .../exemplary/multi-banner-impression.json | 5 +- .../exemplary/single-banner-impression.json | 5 +- .../adocean/adoceantest/supplemental/app.json | 71 +++++++++++++++++++ .../supplemental/bad-response.json | 2 +- .../supplemental/encode-error.json | 2 +- .../supplemental/network-error.json | 2 +- .../adoceantest/supplemental/no-bid.json | 4 +- .../adoceantest/supplemental/no-sizes.json | 4 +- .../supplemental/requests-merge.json | 4 +- static/bidder-info/adocean.yaml | 3 + 11 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 adapters/adocean/adoceantest/supplemental/app.json diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 635cba8c9bc..6a0eb892be4 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -21,7 +21,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const adapterVersion = "1.1.0" +const adapterVersion = "1.2.0" const maxUriLength = 8000 const measurementCode = ` \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n
\n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - NURL: "http://test.com/syspixel?__ads=ip8070-3PJ4q4QyZxnHE6woGe1sQ3&__adt=4122105428549383603&__ade=2&type=tracking&rqc=0w23qR-q7O7MsGkWlR9wOBm8qL7msKBtSKRJV3Pw0a0tZ47xJTnT2JwzqvXgrzPZOLZfI__68S9kCKELawQtZcO6kMyvlPM55uCaRZWng_j5btuPaEuXyA&pab=true", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "{\"type\":\"ID\",\"bid_id\":\"8255649814109237089\",\"placement_id\":\"1995257847363113_1997038003851764\",\"resolved_placement_id\":\"1995257847363113_1997038003851764\",\"sdk_version\":\"4.25.0-appnexus.bidding\",\"device_id\":\"87ECBA49-908A-428F-9DE7-4B9CED4F486C\",\"template\":7,\"payload\":\"null\"}", @@ -103,7 +94,7 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[2] = &CacheObject{ + cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "", @@ -111,14 +102,10 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[3] = &CacheObject{ + cobj[2] = &CacheObject{ IsVideo: true, Value: "", } - cobj[4] = &CacheObject{ - IsVideo: true, - Value: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - } InitPrebidCache(server.URL) ctx := context.TODO() @@ -136,12 +123,6 @@ func TestPrebidClient(t *testing.T) { if cobj[2].UUID != "UUID-3" { t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) } - if cobj[3].UUID != "UUID-4" { - t.Errorf("Fourth object UUID was '%s', should have been 'UUID-4'", cobj[3].UUID) - } - if cobj[4].UUID != "UUID-5" { - t.Errorf("Fifth object UUID was '%s', should have been 'UUID-5'", cobj[4].UUID) - } delay = 5 * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) diff --git a/router/router.go b/router/router.go index 3b51d0730c1..e1c42699225 100644 --- a/router/router.go +++ b/router/router.go @@ -23,7 +23,6 @@ import ( "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rubicon" @@ -160,7 +159,6 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { "pulsepoint": pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint), "rubicon": rubicon.NewRubiconLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), - "lifestreet": lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint), "conversant": conversant.NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), "adform": adform.NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), "sovrn": sovrn.NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/lifestreet.yaml deleted file mode 100644 index 34dc4eca2d9..00000000000 --- a/static/bidder-info/lifestreet.yaml +++ /dev/null @@ -1,11 +0,0 @@ -maintainer: - email: "mobile.tech@lifestreet.com" -gvlVendorID: 67 -capabilities: - app: - mediaTypes: - - banner - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-params/lifestreet.json b/static/bidder-params/lifestreet.json deleted file mode 100644 index 2190d761e69..00000000000 --- a/static/bidder-params/lifestreet.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Lifestreet Adapter Params", - "description": "A schema which validates params accepted by the Lifestreet adapter", - "type": "object", - "properties": { - "slot_tag": { - "type": "string", - "description": "A tag which identifies the ad slot" - } - }, - "required": ["slot_tag"] -} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1c4e809e72b..a3f32320796 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -51,7 +51,6 @@ import ( "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" @@ -149,7 +148,6 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 10aaafd5985..39da3955fd9 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -60,7 +60,6 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderJixie): syncConfig, string(openrtb_ext.BidderKrushmedia): syncConfig, - string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, From 7267f6e855f9f38eda179ea3c4f1629a22b62766 Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:53:26 +0300 Subject: [PATCH 430/603] New Adapter: E-Volution (#1868) --- adapters/e_volution/evolution.go | 112 ++++++++++ adapters/e_volution/evolution_test.go | 18 ++ .../exemplary/banner-without-mediatype.json | 168 +++++++++++++++ .../evolutiontest/exemplary/banner.json | 174 +++++++++++++++ .../evolutiontest/exemplary/native.json | 181 ++++++++++++++++ .../evolutiontest/exemplary/video.json | 200 ++++++++++++++++++ .../evolutiontest/params/race/banner.json | 3 + .../evolutiontest/params/race/native.json | 3 + .../evolutiontest/params/race/video.json | 3 + .../supplemental/bad-response.json | 158 ++++++++++++++ .../supplemental/empty-seatbid.json | 149 +++++++++++++ .../supplemental/status-204.json | 126 +++++++++++ .../supplemental/status-400.json | 133 ++++++++++++ .../supplemental/status-503.json | 125 +++++++++++ .../supplemental/unexpected-status.json | 132 ++++++++++++ adapters/e_volution/params_test.go | 49 +++++ adapters/e_volution/usersync.go | 12 ++ adapters/e_volution/usersync_test.go | 33 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/e_volution.yaml | 14 ++ static/bidder-params/e_volution.json | 13 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1815 insertions(+) create mode 100644 adapters/e_volution/evolution.go create mode 100644 adapters/e_volution/evolution_test.go create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/native.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/video.json create mode 100644 adapters/e_volution/evolutiontest/params/race/banner.json create mode 100644 adapters/e_volution/evolutiontest/params/race/native.json create mode 100644 adapters/e_volution/evolutiontest/params/race/video.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/bad-response.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-204.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-400.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-503.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/unexpected-status.json create mode 100644 adapters/e_volution/params_test.go create mode 100644 adapters/e_volution/usersync.go create mode 100644 adapters/e_volution/usersync_test.go create mode 100644 static/bidder-info/e_volution.yaml create mode 100644 static/bidder-params/e_volution.json diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go new file mode 100644 index 00000000000..26df301cdb7 --- /dev/null +++ b/adapters/e_volution/evolution.go @@ -0,0 +1,112 @@ +package evolution + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + URI string +} + +type bidExt struct { + MediaType openrtb_ext.BidType `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.URI, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, nil + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderRawResponse.StatusCode), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad response, %s", err), + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Empty seatbid"), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := range sb.Bid { + var bidType openrtb_ext.BidType + var bidExt bidExt + if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { + bidType = openrtb_ext.BidTypeBanner + } else { + bidType = bidExt.MediaType + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + return bidResponse, nil +} diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go new file mode 100644 index 00000000000..1d2ee7ef9a6 --- /dev/null +++ b/adapters/e_volution/evolution_test.go @@ -0,0 +1,18 @@ +package evolution + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderEVolution, config.Adapter{ + Endpoint: "http://service.e-volution.ai/pbserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "evolutiontest", bidder) +} diff --git a/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json new file mode 100644 index 00000000000..251fe8c6f87 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/banner.json b/adapters/e_volution/evolutiontest/exemplary/banner.json new file mode 100644 index 00000000000..68fda4907e2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/native.json b/adapters/e_volution/evolutiontest/exemplary/native.json new file mode 100644 index 00000000000..724f55f6b8b --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/native.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/video.json b/adapters/e_volution/evolutiontest/exemplary/video.json new file mode 100644 index 00000000000..f7a03146918 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/video.json @@ -0,0 +1,200 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "bidid": "ccbd63285c0e7b69602d90319bda6be4", + "seatbid": [{ + "bid": [{ + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/banner.json b/adapters/e_volution/evolutiontest/params/race/banner.json new file mode 100644 index 00000000000..0a04c95b072 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test_banner" +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/native.json b/adapters/e_volution/evolutiontest/params/race/native.json new file mode 100644 index 00000000000..032b9dd56d8 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test_native" +} diff --git a/adapters/e_volution/evolutiontest/params/race/video.json b/adapters/e_volution/evolutiontest/params/race/video.json new file mode 100644 index 00000000000..87071003920 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test_video" +} diff --git a/adapters/e_volution/evolutiontest/supplemental/bad-response.json b/adapters/e_volution/evolutiontest/supplemental/bad-response.json new file mode 100644 index 00000000000..75f3cb455af --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/bad-response.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..c9a103aea39 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "Empty seatbid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-204.json b/adapters/e_volution/evolutiontest/supplemental/status-204.json new file mode 100644 index 00000000000..85e89873fd2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-204.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-400.json b/adapters/e_volution/evolutiontest/supplemental/status-400.json new file mode 100644 index 00000000000..b26e827200e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-400.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-503.json b/adapters/e_volution/evolutiontest/supplemental/status-503.json new file mode 100644 index 00000000000..0f289ea8d3e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-503.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..5d0df32383e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 401 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go new file mode 100644 index 00000000000..2d3602fd72b --- /dev/null +++ b/adapters/e_volution/params_test.go @@ -0,0 +1,49 @@ +package evolution + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "24" }`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderEVolution, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected evolution params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderEVolution, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/e_volution/usersync.go b/adapters/e_volution/usersync.go new file mode 100644 index 00000000000..f22784d018b --- /dev/null +++ b/adapters/e_volution/usersync.go @@ -0,0 +1,12 @@ +package evolution + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewEvolutionSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("e_volution", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/e_volution/usersync_test.go b/adapters/e_volution/usersync_test.go new file mode 100644 index 00000000000..d7a3eba5f0a --- /dev/null +++ b/adapters/e_volution/usersync_test.go @@ -0,0 +1,33 @@ +package evolution + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewEvolutionSyncer(t *testing.T) { + syncURL := "https://sync.test.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewEvolutionSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://sync.test.com/pbserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 2d8fb129e42..a259a9aa4e1 100644 --- a/config/config.go +++ b/config/config.go @@ -604,6 +604,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderDMX doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEVolution, "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3De_volution%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderEpom doesn't have a good default. @@ -862,6 +863,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb") v.SetDefault("adapters.epom.disabled", true) + v.SetDefault("adapters.e_volution.endpoint", "http://service.e-volution.ai/pbserver") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 4965f7f5019..a773d268604 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -46,6 +46,7 @@ import ( "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -169,6 +170,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEngageBDR: engagebdr.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, + openrtb_ext.BidderEVolution: evolution.Builder, openrtb_ext.BidderGamma: gamma.Builder, openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGrid: grid.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 082498d2893..ea6c809fd9b 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -121,6 +121,7 @@ const ( BidderEngageBDR BidderName = "engagebdr" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" + BidderEVolution BidderName = "e_volution" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" @@ -240,6 +241,7 @@ func CoreBidderNames() []BidderName { BidderEngageBDR, BidderEPlanning, BidderEpom, + BidderEVolution, BidderGamma, BidderGamoshi, BidderGrid, diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml new file mode 100644 index 00000000000..6ea9dc7bac2 --- /dev/null +++ b/static/bidder-info/e_volution.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "admin@e-volution.ai" +gvlVendorID: 957 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/e_volution.json b/static/bidder-params/e_volution.json new file mode 100644 index 00000000000..18de2a6062d --- /dev/null +++ b/static/bidder-params/e_volution.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "E-volution Adapter Params", + "description": "A schema which validates params accepted by the E-volution adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index a3f32320796..88752f4d7d7 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -39,6 +39,7 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -136,6 +137,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderEVolution, evolution.NewEvolutionSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAudienceNetwork, audienceNetwork.NewFacebookSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 39da3955fd9..2ebd541d015 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -51,6 +51,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig, string(openrtb_ext.BidderEPlanning): syncConfig, + string(openrtb_ext.BidderEVolution): syncConfig, string(openrtb_ext.BidderGamma): syncConfig, string(openrtb_ext.BidderGamoshi): syncConfig, string(openrtb_ext.BidderGrid): syncConfig, From 08ad3eef8fba678f4b82461184651b46f49b6d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Thu, 10 Jun 2021 18:55:20 +0200 Subject: [PATCH 431/603] [criteo] accept zoneId and networkId alternate case (#1869) --- .../supplemental/multislots-alt-case.json | 232 ++++++++++++++++++ adapters/criteo/params_test.go | 7 + static/bidder-params/criteo.json | 78 +++--- 3 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 adapters/criteo/criteotest/supplemental/multislots-alt-case.json diff --git a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json new file mode 100644 index 00000000000..beb855e3f2b --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json @@ -0,0 +1,232 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 123456, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 7891011, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 121314, + "networkId": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index 73ace617b2d..9c836769aca 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -41,9 +41,13 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"zoneid": 123456}`, + `{"zoneId": 123456}`, `{"networkid": 78910}`, + `{"networkId": 78910}`, `{"zoneid": 123456, "networkid": 78910}`, + `{"zoneId": 123456, "networkId": 78910}`, `{"zoneid": 0, "networkid": 0}`, + `{"zoneId": 0, "networkId": 0}`, } var invalidParams = []string{ @@ -55,8 +59,11 @@ var invalidParams = []string{ `[]`, `{}`, `{"zoneid": -123}`, + `{"zoneId": -123}`, `{"networkid": -321}`, + `{"networkId": -321}`, `{"zoneid": -123, "networkid": -321}`, + `{"zoneId": -123, "networkId": -321}`, `{"zoneid": -1}`, `{"networkid": -1}`, `{"zoneid": -1, "networkid": -1}`, diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json index 9d348a7eded..88c6fba5d3a 100644 --- a/static/bidder-params/criteo.json +++ b/static/bidder-params/criteo.json @@ -1,30 +1,50 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Criteo adapter params", - "description": "The schema to validate Criteo specific params accepted by Criteo adapter", - "type": "object", - "properties": { - "zoneid": { - "type": "number", - "description": "Impression's zone ID.", - "minimum": 0 - }, - "networkid": { - "type": "number", - "description": "Impression's network ID.", - "minimum": 0 - } - }, - "anyOf": [ - { - "required": [ - "zoneid" - ] - }, - { - "required": [ - "networkid" - ] - } - ] +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Criteo adapter params", + "description": "The schema to validate Criteo specific params accepted by Criteo adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "integer", + "description": "Impression's zone ID.", + "minimum": 0 + }, + "zoneId": { + "type": "integer", + "description": "Impression's zone ID, preferred.", + "minimum": 0 + }, + "networkid": { + "type": "integer", + "description": "Impression's network ID.", + "minimum": 0 + }, + "networkId": { + "type": "integer", + "description": "Impression's network ID, preferred.", + "minimum": 0 + } + }, + "anyOf": [ + { + "required": [ + "zoneid" + ] + }, + { + "required": [ + "zoneId" + ] + }, + { + "required": [ + "networkid" + ] + }, + { + "required": [ + "networkId" + ] + } + ] } \ No newline at end of file From 486926600eeb669f69a1b753e583cca311e873d0 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 10 Jun 2021 10:17:18 -0700 Subject: [PATCH 432/603] Unit test random map order fix (#1887) Co-authored-by: Veronika Solovei --- exchange/exchange_test.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index a03c4786b79..d3bcf082cf2 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2623,26 +2623,42 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) totalNumberOfbids := 0 + //due to random map order we need to identify what bidder was first + firstBidderIndicator := true + if bidsFromFirstBidder.bids != nil { totalNumberOfbids += len(bidsFromFirstBidder.bids) } if bidsFromSecondBidder.bids != nil { + firstBidderIndicator = false totalNumberOfbids += len(bidsFromSecondBidder.bids) } assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned") + assert.Len(t, rejections, 2, "2 bids should be de-duplicated") - assert.Len(t, adapterBids[bidderNameApn1].bids, 0) - assert.Len(t, adapterBids[bidderNameApn2].bids, 2) + if firstBidderIndicator { + assert.Len(t, adapterBids[bidderNameApn1].bids, 2) + assert.Len(t, adapterBids[bidderNameApn2].bids, 0) - assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id") - assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id") + assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].bids[1].bid.ID, "Incorrect expected bid 2 id") - assert.Len(t, rejections, 2, "2 bids should be de-duplicated") - assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") - assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } else { + assert.Len(t, adapterBids[bidderNameApn1].bids, 0) + assert.Len(t, adapterBids[bidderNameApn2].bids, 2) + + assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id") + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } } func TestRemoveBidById(t *testing.T) { From 1993de4cb8db5537d83ede873b50902e24799808 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 10 Jun 2021 13:57:51 -0400 Subject: [PATCH 433/603] Request Provided Currency Rates (#1753) --- currency/aggregate_conversions.go | 41 ++ currency/aggregate_conversions_test.go | 89 ++++ currency/constant_rates.go | 4 +- currency/errors.go | 13 + currency/rates.go | 14 +- endpoints/openrtb2/auction.go | 29 ++ endpoints/openrtb2/auction_test.go | 245 ++++++++++- .../no-account/not-required-no-acct.json | 1 + .../with-account/required-with-acct.json | 1 + .../aliased/multiple-alias.json | 1 + .../sample-requests/aliased/simple.json | 25 +- .../errors/conversion-disabled.json | 46 ++ ...rates-currency-missing-usepbs-default.json | 52 +++ ...m-rates-currency-missing-usepbs-false.json | 53 +++ .../custom-rates-empty-usepbs-false.json | 49 +++ .../custom-rates-invalid-usepbs-false.json | 53 +++ .../valid/conversion-disabled.json | 62 +++ ...om-rate-not-found-usepbsrates-default.json | 70 ++++ ...om-rates-override-usepbsrates-default.json | 69 +++ ...stom-rates-override-usepbsrates-false.json | 70 ++++ ...ustom-rates-override-usepbsrates-true.json | 70 ++++ .../valid/reverse-currency-conversion.json | 66 +++ .../valid/server-rates-usepbsrates-true.json | 70 ++++ .../errors/no-conversion-found.json | 38 ++ .../server-rates/valid/simple-conversion.json | 55 +++ .../disabled/good/partial.json | 1 + .../valid-fpd-allowed-with-ext-bidder.json | 3 +- .../valid-fpd-allowed-with-prebid-bidder.json | 3 +- .../valid-native/asset-img-no-hmin.json | 25 +- .../valid-native/asset-img-no-wmin.json | 25 +- .../valid-native/asset-with-id.json | 1 + .../valid-native/asset-with-no-id.json | 1 + .../valid-native/assets-with-unique-ids.json | 1 + .../context-product-compatible-subtype.json | 25 +- .../context-social-compatible-subtype.json | 25 +- .../eventtracker-exchange-specific.json | 3 +- .../valid-native/request-no-context.json | 1 + .../valid-native/request-plcmttype-empty.json | 1 + .../video-asset-event-tracker.json | 1 + .../valid-native/with-video-asset.json | 1 + .../valid-whole/exemplary/all-ext.json | 1 + .../valid-whole/exemplary/prebid-test-ad.json | 1 + .../valid-whole/exemplary/skadn.json | 3 +- errortypes/code.go | 2 + exchange/bidder_test.go | 20 +- exchange/exchange.go | 30 +- exchange/exchange_test.go | 395 +++++++++++++++++- go.mod | 2 +- go.sum | 3 +- openrtb_ext/request.go | 7 + 50 files changed, 1761 insertions(+), 106 deletions(-) create mode 100644 currency/aggregate_conversions.go create mode 100644 currency/aggregate_conversions_test.go create mode 100644 currency/errors.go create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go new file mode 100644 index 00000000000..53c5ebff4b6 --- /dev/null +++ b/currency/aggregate_conversions.go @@ -0,0 +1,41 @@ +package currency + +// AggregateConversions contains both the request-defined currency rate +// map found in request.ext.prebid.currency and the currencies conversion +// rates fetched with the RateConverter object defined in rate_converter.go +// It implements the Conversions interface. +type AggregateConversions struct { + customRates, serverRates Conversions +} + +// NewAggregateConversions expects both customRates and pbsRates to not be nil +func NewAggregateConversions(customRates, pbsRates Conversions) *AggregateConversions { + return &AggregateConversions{ + customRates: customRates, + serverRates: pbsRates, + } +} + +// GetRate returns the conversion rate between two currencies prioritizing +// the customRates currency rate over that of the PBS currency rate service +// returns an error if both Conversions objects return error. +func (re *AggregateConversions) GetRate(from string, to string) (float64, error) { + rate, err := re.customRates.GetRate(from, to) + if err == nil { + return rate, nil + } else if _, isMissingRateErr := err.(ConversionRateNotFound); !isMissingRateErr { + // other error, return the error + return 0, err + } + + // because the custom rates' GetRate() call returned an error other than "conversion + // rate not found", there's nothing wrong with the 3 letter currency code so let's + // try the PBS rates instead + return re.serverRates.GetRate(from, to) +} + +// GetRates is not implemented for AggregateConversions . There is no need to call +// this function for this scenario. +func (r *AggregateConversions) GetRates() *map[string]map[string]float64 { + return nil +} diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go new file mode 100644 index 00000000000..35ca51a1fe7 --- /dev/null +++ b/currency/aggregate_conversions_test.go @@ -0,0 +1,89 @@ +package currency + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGroupedGetRate(t *testing.T) { + + // Setup: + customRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 3.00, + "EUR": 2.00, + }, + }) + + pbsRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 4.00, + "MXN": 10.00, + }, + }) + aggregateConversions := NewAggregateConversions(customRates, pbsRates) + + // Test cases: + type aTest struct { + desc string + from string + to string + expectedRate float64 + } + + testGroups := []struct { + expectedError error + testCases []aTest + }{ + { + expectedError: nil, + testCases: []aTest{ + {"Found in both, return custom rate", "USD", "GBP", 3.00}, + {"Found in both, return inverse custom rate", "GBP", "USD", 1 / 3.00}, + {"Found in custom rates only", "USD", "EUR", 2.00}, + {"Found in PBS rates only", "USD", "MXN", 10.00}, + {"Found in PBS rates only, return inverse", "MXN", "USD", 1 / 10.00}, + {"Same currency, return unitary rate", "USD", "USD", 1}, + }, + }, + { + expectedError: errors.New("currency: tag is not well-formed"), + testCases: []aTest{ + {"From-currency three-digit code malformed", "XX", "EUR", 0}, + {"To-currency three-digit code malformed", "GBP", "", 0}, + {"Both currencies malformed", "", "", 0}, + }, + }, + { + expectedError: errors.New("currency: tag is not a recognized currency"), + testCases: []aTest{ + {"From-currency three-digit code not found", "FOO", "EUR", 0}, + {"To-currency three-digit code not found", "GBP", "BAR", 0}, + }, + }, + { + expectedError: ConversionRateNotFound{"GBP", "EUR"}, + testCases: []aTest{ + {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, + }, + }, + } + + for _, group := range testGroups { + for _, tc := range group.testCases { + // Execute: + rate, err := aggregateConversions.GetRate(tc.from, tc.to) + + // Verify: + assert.Equal(t, tc.expectedRate, rate, "conversion rate doesn't match the expected rate: %s\n", tc.desc) + if group.expectedError != nil { + assert.Error(t, err, "error doesn't match expected: %s\n", tc.desc) + } else { + assert.NoError(t, err, "err should be nil: %s\n", tc.desc) + } + } + } +} diff --git a/currency/constant_rates.go b/currency/constant_rates.go index 26471a966a5..dde317d809e 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -1,8 +1,6 @@ package currency import ( - "fmt" - "golang.org/x/text/currency" ) @@ -29,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go new file mode 100644 index 00000000000..d764c15b984 --- /dev/null +++ b/currency/errors.go @@ -0,0 +1,13 @@ +package currency + +import "fmt" + +// ConversionRateNotFound is thrown by the currency.Conversions GetRate(from string, to string) method +// when the conversion rate between the two currencies, nor its reciprocal, can be found. +type ConversionRateNotFound struct { + FromCur, ToCur string +} + +func (err ConversionRateNotFound) Error() string { + return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur) +} diff --git a/currency/rates.go b/currency/rates.go index a3ae5f30fd5..62914c4b2e2 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -3,7 +3,6 @@ package currency import ( "encoding/json" "errors" - "fmt" "time" "golang.org/x/text/currency" @@ -45,8 +44,11 @@ func (r *Rates) UnmarshalJSON(b []byte) error { return nil } -// GetRate returns the conversion rate between two currencies -// returns an error in case the conversion rate between the two given currencies is not in the currencies rates map +// GetRate returns the conversion rate between two currencies or: +// - An error if one of the currency strings is not well-formed +// - An error if any of the currency strings is not a recognized currency code. +// - A MissingConversionRate error in case the conversion rate between the two +// given currencies is not in the currencies rates map func (r *Rates) GetRate(from string, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) @@ -63,12 +65,12 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { if r.Conversions != nil { if conversion, present := r.Conversions[fromUnit.String()][toUnit.String()]; present { // In case we have an entry FROM -> TO - return conversion, err + return conversion, nil } else if conversion, present := r.Conversions[toUnit.String()][fromUnit.String()]; present { // In case we have an entry TO -> FROM - return 1 / conversion, err + return 1 / conversion, nil } - return 0, fmt.Errorf("Currency conversion rate not found: '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 8913e90791d..d8a7fa689b9 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -37,6 +37,7 @@ import ( "github.com/prebid/prebid-server/util/httputil" "github.com/prebid/prebid-server/util/iputil" "golang.org/x/net/publicsuffix" + "golang.org/x/text/currency" ) const storedRequestTimeoutMillis = 50 @@ -343,6 +344,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { if err := deps.validateEidPermissions(bidExt, aliases); err != nil { return []error{err} } + + if err := validateCustomRates(bidExt.Prebid.CurrencyConversions); err != nil { + return []error{err} + } } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { @@ -437,6 +442,30 @@ func validateSChains(req *openrtb_ext.ExtRequest) error { return err } +// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in +// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual +// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty. +func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error { + if bidReqCurrencyRates == nil { + return nil + } + + for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates { + // Check if fromCurrency is a valid 3-letter currency code + if _, err := currency.ParseISO(fromCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)} + } + + // Check if currencies mapped to fromCurrency are valid 3-letter currency codes + for toCurrency := range rates { + if _, err := currency.ParseISO(toCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)} + } + } + } + return nil +} + func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error { if req == nil || req.Prebid.Data == nil { return nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 3d40d35b068..bcdac13dc06 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -24,6 +24,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/metrics" @@ -45,11 +46,13 @@ type testCase struct { } type testConfigValues struct { - AccountRequired bool `json:"accountRequired"` - AliasJSON string `json:"aliases"` - BlacklistedAccounts []string `json:"blacklistedAccts"` - BlacklistedApps []string `json:"blacklistedApps"` - DisabledAdapters []string `json:"disabledAdapters"` + AccountRequired bool `json:"accountRequired"` + AliasJSON string `json:"aliases"` + BlacklistedAccounts []string `json:"blacklistedAccts"` + BlacklistedApps []string `json:"blacklistedApps"` + DisabledAdapters []string `json:"disabledAdapters"` + CurrencyRates map[string]map[string]float64 `json:"currencyRates"` + MockBidder mockBidExchangeBidder `json:"mockBidder"` } func TestJsonSampleRequests(t *testing.T) { @@ -105,6 +108,22 @@ func TestJsonSampleRequests(t *testing.T) { "Requests with first party data context info found in imp[i].ext.prebid.bidder,context", "first-party-data", }, + { + "Assert we correctly use the server conversion rates when needed", + "currency-conversion/server-rates/valid", + }, + { + "Assert we correctly throw an error when no conversion rate was found in the server conversions map", + "currency-conversion/server-rates/errors", + }, + { + "Assert we correctly use request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/valid", + }, + { + "Assert we correctly validate request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/errors", + }, } for _, test := range testSuites { testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", test.sampleRequestsSubDir)) @@ -248,6 +267,7 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.BidID, actualBidResponse.BidID, "BidResponse.BidID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.NBR, actualBidResponse.NBR, "BidResponse.NBR doesn't match expected. Test: %s\n", testFile) + assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile) //Assert []SeatBid and their Bid elements independently of their order assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile) @@ -441,8 +461,10 @@ func doRequest(t *testing.T, test testCase) (int, string) { bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates) + endpoint, _ := NewEndpoint( - &mockBidExchange{}, + mockExchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1184,6 +1206,113 @@ func TestContentType(t *testing.T) { } } +func TestValidateCustomRates(t *testing.T) { + boolTrue := true + boolFalse := false + + testCases := []struct { + desc string + inBidReqCurrencies *openrtb_ext.ExtRequestCurrency + outCurrencyError error + }{ + { + desc: "nil input, no errors expected", + inBidReqCurrencies: nil, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolFalse, + }, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolTrue, + }, + outCurrencyError: nil, + }, + { + desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "FOO": 10.0, + "MXN": 0.05, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "MXN": 0.05, + "CAD": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "All 3-digit currency codes exist, expect no error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "MXN": 0.05, + }, + "MXN": { + "JPY": 10.0, + "EUR": 10.95, + }, + }, + UsePBSRates: &boolFalse, + }, + }, + } + + for _, tc := range testCases { + actualErr := validateCustomRates(tc.inBidReqCurrencies) + + assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc) + } +} + func TestValidateImpExt(t *testing.T) { type testCase struct { description string @@ -2590,7 +2719,49 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReque } type mockBidExchange struct { - gotRequest *openrtb2.BidRequest + mockBidder mockBidExchangeBidder + pbsRates map[string]map[string]float64 +} + +func newMockBidExchange(bidder mockBidExchangeBidder, mockCurrencyConversionRates map[string]map[string]float64) *mockBidExchange { + if bidder.BidCurrency == "" { + bidder.BidCurrency = "USD" + } + + return &mockBidExchange{ + mockBidder: bidder, + pbsRates: mockCurrencyConversionRates, + } +} + +// getAuctionCurrencyRates copies the logic of the exchange package for testing purposes +func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if customRates == nil { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), e.pbsRates) + } + + usePbsRates := true + if customRates.UsePBSRates != nil { + usePbsRates = *customRates.UsePBSRates + } + + if !usePbsRates { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), customRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(customRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return currency.NewRates(time.Now(), e.pbsRates) + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, customRates.ConversionRates), currency.NewRates(time.Now(), e.pbsRates)) } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext @@ -2601,6 +2772,36 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq BidID: "test bid id", NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), } + + // Use currencies inside r.BidRequest.Cur, if any, and convert currencies if needed + if len(r.BidRequest.Cur) == 0 { + r.BidRequest.Cur = []string{"USD"} + } + + var currencyFrom string = e.mockBidder.getBidCurrency() + var conversionRate float64 = 0.00 + var err error + + var requestExt openrtb_ext.ExtRequest + if len(r.BidRequest.Ext) > 0 { + if err := json.Unmarshal(r.BidRequest.Ext, &requestExt); err != nil { + return nil, fmt.Errorf("request.ext is invalid: %v", err) + } + } + + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) + for _, bidReqCur := range r.BidRequest.Cur { + if conversionRate, err = conversions.GetRate(currencyFrom, bidReqCur); err == nil { + bidResponse.Cur = bidReqCur + break + } + } + + if conversionRate == 0 { + // Can't have bids if there's not even a 1 USD to 1 USD conversion rate + return nil, errors.New("Can't produce bid with no valid currency to use or currency conversion to convert to.") + } + if len(r.BidRequest.Imp) > 0 { var SeatBidMap = make(map[string]openrtb2.SeatBid, 0) for _, imp := range r.BidRequest.Imp { @@ -2625,9 +2826,17 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq for bidderNameOrAlias := range bidderExts { if isBidderToValidate(bidderNameOrAlias) { if val, ok := SeatBidMap[bidderNameOrAlias]; ok { - val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) + val.Bid = append(val.Bid, openrtb2.Bid{ID: e.mockBidder.getBidId(bidderNameOrAlias)}) } else { - SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} + SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{ + Seat: e.mockBidder.getSeatName(bidderNameOrAlias), + Bid: []openrtb2.Bid{ + { + ID: e.mockBidder.getBidId(bidderNameOrAlias), + Price: e.mockBidder.getBidPrice() * conversionRate, + }, + }, + } } } } @@ -2640,6 +2849,24 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq return bidResponse, nil } +type mockBidExchangeBidder struct { + BidCurrency string `json:"currency"` + BidPrice float64 `json:"price"` +} + +func (bidder mockBidExchangeBidder) getBidCurrency() string { + return bidder.BidCurrency +} +func (bidder mockBidExchangeBidder) getBidPrice() float64 { + return bidder.BidPrice +} +func (bidder mockBidExchangeBidder) getSeatName(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bids", bidderNameOrAlias) +} +func (bidder mockBidExchangeBidder) getBidId(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bid", bidderNameOrAlias) +} + type brokenExchange struct{} func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { diff --git a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json index c3ab09d4883..75c859d212b 100644 --- a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json @@ -66,6 +66,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json index a72d184c81c..ae930384499 100644 --- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json @@ -68,6 +68,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json index 55e45041e6e..00906c89772 100644 --- a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json +++ b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json @@ -87,6 +87,7 @@ } ], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/simple.json index a99907ab370..677d3d8cf53 100644 --- a/endpoints/openrtb2/sample-requests/aliased/simple.json +++ b/endpoints/openrtb2/sample-requests/aliased/simple.json @@ -27,19 +27,20 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, - "seatbid": [ - { - "bid": [ - { - "id": "alias1-bid", - "impid": "", - "price": 0 - } - ], - "seat": "alias1-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "alias1-bid", + "impid": "", + "price": 0 + } + ], + "seat": "alias1-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json new file mode 100644 index 00000000000..03877031294 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json @@ -0,0 +1,46 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates is false, a conversion is needed but conversions are disabled", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json new file mode 100644 index 00000000000..6a727e9615c --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json @@ -0,0 +1,52 @@ +{ + "description": "currency in request.cur cannot be converted because conversion rate not found in either custom currency rates nor server rates. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["GBP"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json new file mode 100644 index 00000000000..5549fa9b688 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "currency in request.cur cannot be converted because usepbsrates set to false not allowing for PBS to use its rates. Default to price of 0", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 2.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json new file mode 100644 index 00000000000..f4e19f3a4c5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json @@ -0,0 +1,49 @@ +{ + "description": "usepbsrates set to false forces BidRequest to use custom currency rates but bidRequest.ext.prebid.currency.rates field is empty", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json new file mode 100644 index 00000000000..39857650f12 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "False usepbsrates forces BidRequest use custom currency rates but bidRequest.ext.prebid.currency.rates field comes with invalid currency codes", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "FOO": 10.0 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: currency code " +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json new file mode 100644 index 00000000000..0741ea4d315 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json @@ -0,0 +1,62 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates set to false, request succeeded because no conversion was needed", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 1.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json new file mode 100644 index 00000000000..fb65a852355 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json @@ -0,0 +1,70 @@ +{ + "description": "request comes with custom rates but request.cur currency is only found in the server rates. Error wasn't thrown because usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 15.00, + "EUR": 0.85 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json new file mode 100644 index 00000000000..80790a52543 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json @@ -0,0 +1,69 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json new file mode 100644 index 00000000000..ef372c1cf66 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json @@ -0,0 +1,70 @@ +{ + "description": "request.ext.prebid.currency substitutes those of the currency conversion server because usepbsrates is false", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json new file mode 100644 index 00000000000..276e8da43c2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json new file mode 100644 index 00000000000..624f0784dac --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json @@ -0,0 +1,66 @@ +{ + "description": "USD BidRequest gets converted because mockbidder bids in foreign currency, custom conversion rate is used", + "config": { + "currencyRates":{ + "USD": { + "MXN": 8.00 + } + }, + "mockBidder": { + "currency": "MXN", + "price": 20.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 10.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "nbr":0, + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json new file mode 100644 index 00000000000..929c2e0cbd5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "CAD": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json new file mode 100644 index 00000000000..dc0d7ce6042 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json @@ -0,0 +1,38 @@ +{ + "description": "bid request calls for a bid in foreign currency MXN but conversion rate is not found in the currency conversion service.", + "config": { + "currencyRates":{ + "USD": { + "GBP": 0.80 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json new file mode 100644 index 00000000000..84788d5ada1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json @@ -0,0 +1,55 @@ +{ + "description": "bid request calls for a bid in foreign currency but mockbidder bids in USD. Conversion rate is applied", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json index 3549abaa934..735e7c5ede1 100644 --- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json +++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json @@ -58,6 +58,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json index c36ae0cd41d..a4b716b2040 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json @@ -43,7 +43,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json index ad6298db39a..27e8c46d9d7 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json @@ -47,7 +47,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json index 15af8551da6..e556b15d4f2 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json index 5d986bcf755..06673bcdf32 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json index 1e55cdda63f..9b8763491a3 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json index 36a1745cb19..22ffc7f50d8 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json index 98cdeedadbe..e60e2028637 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json +++ b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json index dbf7b9c5e0d..a3b7101d8d5 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json index 41fb833d770..77e8ce10a41 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json index 501e7ef5016..214031177ca 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json +++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json @@ -22,6 +22,7 @@ "id": "req-id", "bidid": "test bid id", "nbr": 0, + "cur": "USD", "seatbid": [{ "bid": [{ "id": "appnexus-bid", @@ -32,4 +33,4 @@ }] }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json index 1ad97c8ff8f..5ebc4e697e4 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json index 88af803684d..5518b7a06bc 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json index ab192e14881..fcc7b72d62a 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json +++ b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json index 0ec3c993251..f920c52a591 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json +++ b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index f875fa880bc..46af51635f9 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -115,6 +115,7 @@ } ], "bidid":"test bid id", + "cur":"USD", "nbr":0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json index 2c6a34f569e..d592cb66fcb 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json @@ -44,6 +44,7 @@ } ], "bidid": "test bid id", + "cur":"USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json index e238f3c07c7..cb2cec992fe 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json @@ -42,7 +42,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur":"USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/errortypes/code.go b/errortypes/code.go index 2749b978006..869e7d541a4 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,6 +11,7 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode + NoConversionRateErrorCode ) // Defines numeric codes for well-known warnings. @@ -19,6 +20,7 @@ const ( InvalidPrivacyConsentWarningCode = iota + 10000 AccountLevelDebugDisabledWarningCode BidderLevelDebugDisabledWarningCode + DisabledCurrencyConversionWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 3140930d8e6..5fdfe445206 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), + currency.ConversionRateNotFound{"JPY", "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -587,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), - errors.New("Currency conversion rate not found: 'BZD' => 'USD'"), - errors.New("Currency conversion rate not found: 'DKK' => 'USD'"), + currency.ConversionRateNotFound{"JPY", "USD"}, + currency.ConversionRateNotFound{"BZD", "USD"}, + currency.ConversionRateNotFound{"DKK", "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -719,9 +719,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -753,7 +753,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -761,7 +761,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -769,7 +769,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index ba70305f660..c1602aadfcb 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -202,7 +202,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * defer cancel() // Get currency rates conversions for the auction - conversions := e.currencyConverter.Rates() + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader) @@ -972,6 +972,34 @@ func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo return } +func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if requestRates == nil { + // No bidRequest.ext.currency field was found, use PBS rates as usual + return e.currencyConverter.Rates() + } + + // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false + // only if it's explicitly set to false + usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates + + if !usePbsRates { + // At this point, we can safely assume the ConversionRates map is not empty because + // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have + // thrown an error under such conditions. + return currency.NewRates(time.Time{}, requestRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(requestRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return e.currencyConverter.Rates() + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, requestRates.ConversionRates), e.currencyConverter.Rates()) +} + func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) { if bid != nil && bid.bid != nil && auction != nil { if id, found := auction.cacheIds[bid.bid]; found { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index d3bcf082cf2..f778e3ba411 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -86,8 +86,8 @@ func TestNewExchange(t *testing.T) { // 4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... ) // 5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns func TestCharacterEscape(t *testing.T) { - /* 1) Adapter with a '& char in its endpoint property */ - /* https://github.com/prebid/prebid-server/issues/465 */ + // 1) Adapter with a '& char in its endpoint property + // https://github.com/prebid/prebid-server/issues/465 cfg := &config.Configuration{ Adapters: make(map[string]config.Adapter, 1), } @@ -95,7 +95,7 @@ func TestCharacterEscape(t *testing.T) { Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2", //Note the '&' character in there } - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration //Other parameters also needed to create exchange handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) @@ -114,7 +114,7 @@ func TestCharacterEscape(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -147,10 +147,10 @@ func TestCharacterEscape(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList) - /* 5) Assert we have no errors and one '&' character as we are supposed to */ + // 5) Assert we have no errors and one '&' character as we are supposed to if err != nil { t.Errorf("exchange.buildBidResponse returned unexpected error: %v", err) } @@ -507,6 +507,366 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } +func TestOverrideWithCustomCurrency(t *testing.T) { + + mockCurrencyClient := &mockCurrencyRatesClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + + type testIn struct { + customCurrencyRates json.RawMessage + bidRequestCurrency string + } + type testResults struct { + numBids int + bidRespPrice float64 + bidRespCurrency string + } + + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "Blank currency field in ext. bidRequest comes with a valid currency but conversion rate was not found in PBS. Return no bids", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ "prebid": { "currency": {} } } `), + bidRequestCurrency: "GBP", + }, + expected: testResults{}, + }, + { + desc: "valid request.ext.prebid.currency, expect custom rates to override those of the currency rate server", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 20.00, + "EUR": 10.95 + } + } + } + } + }`), + bidRequestCurrency: "MXN", + }, + expected: testResults{ + numBids: 1, + bidRespPrice: 20.00, + bidRespCurrency: "MXN", + }, + }, + } + + // Init mock currency conversion service + mockCurrencyConverter.Run() + + // Init an exchange to run an auction from + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + mockAppnexusBidService := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer mockAppnexusBidService.Close() + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + oneDollarBidBidder := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: mockAppnexusBidService.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + } + + e := new(exchange) + e.cache = &wellBehavedCache{} + e.me = &metricsConf.DummyMetricsEngine{} + e.gDPR = gdpr.AlwaysAllow{} + e.currencyConverter = mockCurrencyConverter + e.categoriesFetcher = categoriesFetcher + e.bidIDGenerator = &mockBidIDGenerator{false, false} + + // 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}`)}, + } + + // Run tests + for _, test := range testCases { + + oneDollarBidBidder.bidResponse = &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{Price: 1.00}, + }, + }, + Currency: "USD", + } + + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + } + + // Set custom rates in extension + mockBidRequest.Ext = test.in.customCurrencyRates + + // Set bidRequest currency list + mockBidRequest.Cur = []string{test.in.bidRequestCurrency} + + auctionRequest := AuctionRequest{ + BidRequest: mockBidRequest, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + + // Assertions + assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) + + if test.expected.numBids > 0 { + // Assert out currency + assert.Equal(t, test.expected.bidRespCurrency, outBidResponse.Cur, "Bid response currency is wrong: %s \n", test.desc) + + // Assert returned bid + if !assert.NotNil(t, outBidResponse, "outBidResponse is nil: %s \n", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid, "outBidResponse.SeatBid is empty: %s", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "outBidResponse.SeatBid[0].Bid is empty: %s", test.desc) { + return + } + + // Assert returned bid price matches the currency conversion + assert.Equal(t, test.expected.bidRespPrice, outBidResponse.SeatBid[0].Bid[0].Price, "Bid response seatBid price is wrong: %s", test.desc) + } else { + assert.Len(t, outBidResponse.SeatBid, 0, "outBidResponse.SeatBid should be empty: %s", test.desc) + } + } +} + +func TestGetAuctionCurrencyRates(t *testing.T) { + + pbsRates := map[string]map[string]float64{ + "MXN": { + "USD": 20.13, + "EUR": 27.82, + "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates + }, + } + + customRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // different rate than in pbsRates + "EUR": 27.82, // same as in pbsRates + "GBP": 31.12, // not found in pbsRates at all + }, + } + + expectedRateEngineRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // rates engine will prioritize the value found in custom rates + "EUR": 27.82, // same value in both the engine reads the custom entry first + "JPY": 5.09, // the engine will find it in the pbsRates conversions + "GBP": 31.12, // the engine will find it in the custom conversions + }, + } + + boolTrue := true + boolFalse := false + + type testInput struct { + pbsRates map[string]map[string]float64 + bidExtCurrency *openrtb_ext.ExtRequestCurrency + } + type testOutput struct { + constantRates bool + resultingRates map[string]map[string]float64 + } + testCases := []struct { + desc string + given testInput + expected testOutput + }{ + { + "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: expectedRateEngineRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: nil, + }, + testOutput{ + resultingRates: pbsRates, + }, + }, + { + "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: nil, + }, + testOutput{ + constantRates: true, + }, + }, + } + + for _, tc := range testCases { + + // Test setup: + jsonPbsRates, err := json.Marshal(tc.given.pbsRates) + if err != nil { + t.Fatalf("Failed to marshal PBS rates: %v", err) + } + + // Init mock currency conversion service + mockCurrencyClient := &mockCurrencyRatesClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + mockCurrencyConverter.Run() + + e := new(exchange) + e.currencyConverter = mockCurrencyConverter + + // Run test + auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency) + + // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected + rate, err := auctionRates.GetRate("USD", "USD") + assert.NoError(t, err, tc.desc) + assert.Equal(t, float64(1), rate, tc.desc) + + // If we expect an error, assert we have one along with a conversion rate of zero + if tc.expected.constantRates { + rate, err := auctionRates.GetRate("USD", "MXN") + assert.Error(t, err, tc.desc) + assert.Equal(t, float64(0), rate, tc.desc) + } else { + for fromCurrency, rates := range tc.expected.resultingRates { + for toCurrency, expectedRate := range rates { + actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) + assert.NoError(t, err, tc.desc) + assert.Equal(t, expectedRate, actualRate, tc.desc) + } + } + } + } +} + func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -709,7 +1069,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { testExternalCacheHost := "www.externalprebidcache.net" testExternalCachePath := "endpoints/cache" - /* 1) An adapter */ + // 1) An adapter bidderName := openrtb_ext.BidderName("appnexus") cfg := &config.Configuration{ @@ -730,7 +1090,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterList := make([]openrtb_ext.BidderName, 0, 2) testEngine := metricsConf.NewMetricsEngine(cfg, adapterList) - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() @@ -747,7 +1107,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine) e := NewExchange(adapters, pbc, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} //adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, @@ -849,10 +1209,10 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList) - /* 5) Assert we have no errors and the bid response we expected*/ + // 5) Assert we have no errors and the bid response we expected assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") expectedBidResponse := &openrtb2.BidResponse{ @@ -3251,3 +3611,16 @@ type nilCategoryFetcher struct{} func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } + +// mockCurrencyRatesClient is a simple http client mock returning a constant response body +type mockCurrencyRatesClient struct { + responseBody string +} + +func (m *mockCurrencyRatesClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + }, nil +} diff --git a/go.mod b/go.mod index e3b9ff556d5..6d1fe334390 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,6 @@ require ( github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb - golang.org/x/text v0.3.3 + golang.org/x/text v0.3.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 215f5e68e28..78b21ae139c 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index f14cf196366..606874f196a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -41,6 +41,13 @@ type ExtRequestPrebid struct { // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` + + CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` +} + +type ExtRequestCurrency struct { + ConversionRates map[string]map[string]float64 `json:"rates"` + UsePBSRates *bool `json:"usepbsrates"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains From 613de7e052dcf0933e42fd59183defc4ccb3d2d5 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 10 Jun 2021 12:27:37 -0700 Subject: [PATCH 434/603] Debug override header (#1853) --- config/config.go | 2 + config/config_test.go | 1 + endpoints/openrtb2/video_auction.go | 18 ++--- endpoints/openrtb2/video_auction_test.go | 6 +- exchange/auction.go | 27 +++++--- exchange/auction_test.go | 50 ++++++++++++++ exchange/bidder.go | 28 +++++--- exchange/bidder_test.go | 18 +++-- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +-- exchange/cachetest/debuglog_enabled.json | 2 + exchange/exchange.go | 19 +++--- exchange/exchange_test.go | 68 +++++++++++++------ exchange/exchangetest/debuglog_enabled.json | 2 + .../debuglog_enabled_no_bids.json | 2 + 15 files changed, 186 insertions(+), 71 deletions(-) diff --git a/config/config.go b/config/config.go index a259a9aa4e1..e8dd94a00a5 100644 --- a/config/config.go +++ b/config/config.go @@ -449,6 +449,7 @@ type DefReqFiles struct { type Debug struct { TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"` + OverrideToken string `mapstructure:"override_token"` } func (cfg *Debug) validate(errs []error) []error { @@ -988,6 +989,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("debug.timeout_notification.log", false) v.SetDefault("debug.timeout_notification.sampling_rate", 0.0) v.SetDefault("debug.timeout_notification.fail_only", false) + v.SetDefault("debug.override_token", "") /* IPv4 /* Site Local: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 diff --git a/config/config_test.go b/config/config_test.go index 1d4c00a5cd1..84d3b4794a9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -421,6 +421,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") 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, "") } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 84f0699e011..0af3ba512bb 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -128,11 +128,13 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video) } debugLog := exchange.DebugLog{ - Enabled: strings.EqualFold(debugQuery, "true"), - CacheType: prebid_cache_client.TypeXML, - TTL: cacheTTL, - Regexp: deps.debugLogRegexp, + Enabled: strings.EqualFold(debugQuery, "true"), + CacheType: prebid_cache_client.TypeXML, + TTL: cacheTTL, + Regexp: deps.debugLogRegexp, + DebugOverride: exchange.IsDebugOverrideEnabled(r.Header.Get(exchange.DebugOverrideHeader), deps.cfg.Debug.OverrideToken), } + debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { @@ -157,7 +159,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } resolvedRequest := requestJson - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { debugLog.Data.Request = string(requestJson) if headerBytes, err := json.Marshal(r.Header); err == nil { debugLog.Data.Headers = string(headerBytes) @@ -209,7 +211,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re //create full open rtb req from full video request mergeData(videoBidReq, bidReq) // If debug query param is set, force the response to enable test flag - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { bidReq.Test = 1 } @@ -306,7 +308,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidResp.Ext = response.Ext } - if len(bidResp.AdPods) == 0 && debugLog.Enabled { + if len(bidResp.AdPods) == 0 && debugLog.DebugEnabledOrOverridden { err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) if err != nil { vo.Errors = append(vo.Errors, err) @@ -344,7 +346,7 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P } func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) { - if debugLog != nil && debugLog.Enabled { + if debugLog != nil && debugLog.DebugEnabledOrOverridden { if rawUUID, err := uuid.NewV4(); err == nil { debugLog.CacheKey = rawUUID.String() } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9ede7147686..9f0859a32cd 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1041,8 +1041,10 @@ func TestHandleErrorDebugLog(t *testing.T) { Headers: "test headers string", Response: "test response string", }, - TTL: int64(3600), - Regexp: regexp.MustCompile(`[<>]`), + TTL: int64(3600), + Regexp: regexp.MustCompile(`[<>]`), + DebugOverride: false, + DebugEnabledOrOverridden: true, } handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog) diff --git a/exchange/auction.go b/exchange/auction.go index 3d733daaff8..94e808801d9 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -17,14 +17,21 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" ) +const ( + DebugOverrideHeader string = "x-pbs-debug-override" +) + type DebugLog struct { - Enabled bool - CacheType prebid_cache_client.PayloadType - Data DebugData - TTL int64 - CacheKey string - CacheString string - Regexp *regexp.Regexp + Enabled bool + CacheType prebid_cache_client.PayloadType + Data DebugData + TTL int64 + CacheKey string + CacheString string + Regexp *regexp.Regexp + DebugOverride bool + //little optimization, it stores value of debugLog.Enabled || debugLog.DebugOverride + DebugEnabledOrOverridden bool } type DebugData struct { @@ -47,6 +54,10 @@ func (d *DebugLog) BuildCacheString() { d.CacheString = fmt.Sprintf("%s%s%s%s", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response) } +func IsDebugOverrideEnabled(debugHeader, configOverrideToken string) bool { + return configOverrideToken != "" && debugHeader == configOverrideToken +} + func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout int, errors []error) error { if len(d.Data.Response) == 0 && len(errors) == 0 { d.Data.Response = "No response or errors created" @@ -238,7 +249,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } - if len(toCache) > 0 && debugLog != nil && debugLog.Enabled { + if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden { debugLog.CacheKey = hbCacheID debugLog.BuildCacheString() if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 54f67eb8177..ee064fcb6f1 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -118,6 +118,56 @@ func TestCacheJSON(t *testing.T) { } } +func TestIsDebugOverrideEnabled(t *testing.T) { + type inTest struct { + debugHeader string + configToken string + } + type aTest struct { + desc string + in inTest + result bool + } + testCases := []aTest{ + { + desc: "test debug header is empty, config token is empty", + in: inTest{debugHeader: "", configToken: ""}, + result: false, + }, + { + desc: "test debug header is present, config token is empty", + in: inTest{debugHeader: "TestToken", configToken: ""}, + result: false, + }, + { + desc: "test debug header is empty, config token is present", + in: inTest{debugHeader: "", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, not equal", + in: inTest{debugHeader: "TestToken123", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, equal", + in: inTest{debugHeader: "TestToken", configToken: "TestToken"}, + result: true, + }, + { + desc: "test debug header is present, config token is present, not case equal", + in: inTest{debugHeader: "TestTokeN", configToken: "TestToken"}, + result: false, + }, + } + + for _, test := range testCases { + result := IsDebugOverrideEnabled(test.in.debugHeader, test.in.configToken) + assert.Equal(t, test.result, result, test.desc) + } + +} + // LoadCacheSpec reads and parses a file as a test case. If something goes wrong, it returns an error. func loadCacheSpec(filename string) (*cacheSpec, error) { specData, err := ioutil.ReadFile(filename) diff --git a/exchange/bidder.go b/exchange/bidder.go index c6e2587452a..83466c7d3b0 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -49,7 +49,7 @@ 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, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -126,7 +126,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -176,19 +176,25 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B httpInfo := <-responseChannel // If this is a test bid, capture debugging info from the requests. // Write debug data to ext in case if: + // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - if accountDebugAllowed { - if bidder.config.DebugInfo.Allow { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) - } else { - debugDisabledWarning := errortypes.Warning{ - WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, - Message: "debug turned off for bidder", + if headerDebugAllowed { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugInfo := ctx.Value(DebugContextKey) + if debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) } - errs = append(errs, &debugDisabledWarning) } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 5fdfe445206..deff066200a 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -94,7 +94,7 @@ func TestSingleBidder(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -167,7 +167,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -208,7 +208,7 @@ func TestSetGPCHeader(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -246,7 +246,7 @@ func TestSetGPCHeaderNil(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -304,7 +304,7 @@ func TestMultiBidder(t *testing.T) { } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -681,6 +681,7 @@ func TestMultiCurrencies(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -826,6 +827,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -999,6 +1001,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + false, ) // Verify: @@ -1303,6 +1306,7 @@ func TestMobileNativeTypes(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) var actualValue string @@ -1316,7 +1320,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1537,7 +1541,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 3d2eb0b8e42..aec0948ddde 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -28,8 +28,8 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { + seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) if validationErrors := removeInvalidBids(request, 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 3bb43559856..06973b837c2 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 3) assert.Len(t, errs, 0) } @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 0) assert.Len(t, errs, 5) } @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 2) assert.Len(t, errs, 3) } @@ -246,7 +246,7 @@ func TestCurrencyBids(t *testing.T) { Cur: tc.brqCur, } - seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, expectedValidBids) assert.Len(t, errs, expectedErrs) } @@ -257,6 +257,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json index e6c85c57055..faba3ed690d 100644 --- a/exchange/cachetest/debuglog_enabled.json +++ b/exchange/cachetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchange.go b/exchange/exchange.go index c1602aadfcb..6f0c610a958 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -169,13 +169,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } if debugLog == nil { - debugLog = &DebugLog{Enabled: false} + debugLog = &DebugLog{Enabled: false, DebugEnabledOrOverridden: false} } requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo := requestDebugInfo && r.Account.DebugAllow - debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow + debugInfo := debugLog.DebugEnabledOrOverridden || (requestDebugInfo && r.Account.DebugAllow) + debugLog.Enabled = debugLog.DebugEnabledOrOverridden || r.Account.DebugAllow if debugInfo { ctx = e.makeDebugContext(ctx, debugInfo) @@ -204,7 +204,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * // Get currency rates conversions for the auction conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) var auc *auction var cacheErrs []error @@ -249,7 +249,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { @@ -270,7 +270,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else { bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) @@ -281,7 +281,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } - if !r.Account.DebugAllow && requestDebugInfo { + if !r.Account.DebugAllow && requestDebugInfo && !debugLog.DebugOverride { accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ Code: errortypes.AccountLevelDebugDisabledWarningCode, Message: "debug turned off for account", @@ -414,7 +414,8 @@ func (e *exchange) getAllBids( bidAdjustments map[string]float64, conversions currency.Conversions, accountDebugAllowed bool, - globalPrivacyControlHeader string) ( + globalPrivacyControlHeader string, + headerDebugAllowed bool) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -446,7 +447,7 @@ func (e *exchange) getAllBids( var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) + bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) // Add in time reporting elapsed := time.Since(start) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index f778e3ba411..cd36ad94b22 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -176,8 +176,9 @@ func TestDebugBehaviour(t *testing.T) { } type debugData struct { - bidderLevelDebugAllowed bool - accountLevelDebugAllowed bool + bidderLevelDebugAllowed bool + accountLevelDebugAllowed bool + headerOverrideDebugAllowed bool } type aTest struct { @@ -192,57 +193,78 @@ func TestDebugBehaviour(t *testing.T) { desc: "test flag equals zero, ext debug flag false, no debug info expected", in: inTest{test: 0, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals zero, ext debug flag true, debug info expected", in: inTest{test: 0, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag false, debug info expected", in: inTest{test: 1, debug: false}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag true, debug info expected", in: inTest{test: 1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", in: inTest{test: 2, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: true, }, { desc: "test account level debug disabled", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + debugData: debugData{true, false, false}, generateWarnings: true, }, { - desc: "test bidder level debug disabled", + desc: "test header override enabled when all other debug options are disabled", + in: inTest{test: -1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url debug options are enabled when all other debug options are disabled", in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, - generateWarnings: true, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url and bidder debug options are enabled when account debug option is disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, false, true}, + generateWarnings: false, + }, + { + desc: "test all debug options are enabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true, true}, + generateWarnings: false, }, } @@ -322,9 +344,12 @@ func TestDebugBehaviour(t *testing.T) { WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) auctionRequest.Warnings = errL } - + debugLog := &DebugLog{} + if test.debugData.headerOverrideDebugAllowed { + debugLog = &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + } // Run test - outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog) // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) @@ -338,6 +363,11 @@ func TestDebugBehaviour(t *testing.T) { assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set") assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value") + if test.debugData.headerOverrideDebugAllowed { + assert.Empty(t, actualExt.Warnings, "warnings should be empty") + assert.Empty(t, actualExt.Errors, "errors should be empty") + } + if test.out.debugInfoIncluded { assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug) @@ -357,13 +387,13 @@ func TestDebugBehaviour(t *testing.T) { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } - if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") } - if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { if test.generateWarnings { assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") } else { @@ -3411,7 +3441,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -3590,7 +3620,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 851bda69097..8475482f35b 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json index 4823acf8f16..b9bb15df7fb 100644 --- a/exchange/exchangetest/debuglog_enabled_no_bids.json +++ b/exchange/exchangetest/debuglog_enabled_no_bids.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { From ccb56efe1f23ee101aef3fc604db799a1282f7f9 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:58:47 -0400 Subject: [PATCH 435/603] Remove GDPR TCF1 (#1854) --- config/config.go | 44 +++--- config/config_test.go | 81 +++++++++- exchange/utils_test.go | 57 +++---- gdpr/gdpr.go | 4 +- gdpr/impl.go | 57 +++---- gdpr/impl_test.go | 215 +++++++++++++++----------- gdpr/vendorlist-fetching.go | 28 +--- gdpr/vendorlist-fetching_test.go | 173 ++++----------------- metrics/go_metrics_test.go | 10 -- metrics/metrics.go | 4 - metrics/prometheus/prometheus_test.go | 15 -- privacy/gdpr/policy_test.go | 5 - static/tcf1/fallback_gvl.json | 1 - 13 files changed, 296 insertions(+), 398 deletions(-) delete mode 100644 static/tcf1/fallback_gvl.json diff --git a/config/config.go b/config/config.go index e8dd94a00a5..7e24a00d370 100644 --- a/config/config.go +++ b/config/config.go @@ -197,7 +197,6 @@ type GDPR struct { Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]struct{} - TCF1 TCF1 `mapstructure:"tcf1"` TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` // Deprecated: Use account-level GDPR settings (gdpr.integration_enabled.amp) instead // EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies. @@ -218,9 +217,6 @@ func (cfg *GDPR) validate(errs []error) []error { if cfg.AMPException == true { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } - if cfg.TCF1.FetchGVL == true { - errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")) - } return errs } @@ -237,20 +233,14 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } -// TCF1 defines the TCF1 specific configurations for GDPR -type TCF1 struct { - FetchGVL bool `mapstructure:"fetch_gvl"` // Deprecated: In a future version TCF1 will always use the fallback GVL - FallbackGVLPath string `mapstructure:"fallback_gvl_path"` -} - // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { - Enabled bool `mapstructure:"enabled"` - Purpose1 PurposeDetail `mapstructure:"purpose1"` - Purpose2 PurposeDetail `mapstructure:"purpose2"` - Purpose7 PurposeDetail `mapstructure:"purpose7"` - SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` - PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"` + Enabled bool `mapstructure:"enabled"` + Purpose1 PurposeDetail `mapstructure:"purpose1"` + Purpose2 PurposeDetail `mapstructure:"purpose2"` + Purpose7 PurposeDetail `mapstructure:"purpose7"` + SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` + PurposeOneTreatment PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } // Making a purpose struct so purpose specific details can be added later. @@ -258,7 +248,7 @@ type PurposeDetail struct { Enabled bool `mapstructure:"enabled"` } -type PurposeOneTreatement struct { +type PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -951,16 +941,12 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) - v.SetDefault("gdpr.tcf1.fetch_gvl", false) - v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json") v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) v.SetDefault("gdpr.tcf2.purpose2.enabled", true) v.SetDefault("gdpr.tcf2.purpose4.enabled", true) v.SetDefault("gdpr.tcf2.purpose7.enabled", true) v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", @@ -1015,6 +1001,10 @@ func SetupViper(v *viper.Viper, filename string) { // Migrate config settings to maintain compatibility with old configs migrateConfig(v) + migrateConfigPurposeOneTreatment(v) + + v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) + v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) } func migrateConfig(v *viper.Viper) { @@ -1030,6 +1020,18 @@ func migrateConfig(v *viper.Viper) { } } +func migrateConfigPurposeOneTreatment(v *viper.Viper) { + if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok { + if v.IsSet("gdpr.tcf2.purpose_one_treatment") { + glog.Warning("using gdpr.tcf2.purpose_one_treatment and ignoring deprecated gdpr.tcf2.purpose_one_treatement") + } else { + glog.Warning("gdpr.tcf2.purpose_one_treatement.enabled should be changed to gdpr.tcf2.purpose_one_treatment.enabled") + glog.Warning("gdpr.tcf2.purpose_one_treatement.access_allowed should be changed to gdpr.tcf2.purpose_one_treatment.access_allowed") + v.Set("gdpr.tcf2.purpose_one_treatment", oldConfig) + } + } +} + func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." v.SetDefault(adapterCfgPrefix+bidder+".endpoint", "") diff --git a/config/config_test.go b/config/config_test.go index 84d3b4794a9..f34bd5fa189 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -141,6 +141,8 @@ 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) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.enabled", true, cfg.GDPR.TCF2.PurposeOneTreatment.Enabled) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.access_allowed", true, cfg.GDPR.TCF2.PurposeOneTreatment.AccessAllowed) } var fullConfig = []byte(` @@ -510,6 +512,79 @@ func TestMigrateConfigFromEnv(t *testing.T) { cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) } +func TestMigrateConfigPurposeOneTreatment(t *testing.T) { + oldPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatement: + enabled: true + access_allowed: true + `) + newPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatment: + enabled: true + access_allowed: true + `) + oldAndNewPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatement: + enabled: false + access_allowed: true + purpose_one_treatment: + enabled: true + access_allowed: false + `) + + tests := []struct { + description string + config []byte + wantPurpose1TreatmentEnabled bool + wantPurpose1TreatmentAccessAllowed bool + }{ + { + description: "New config and old config not set", + config: []byte{}, + }, + { + description: "New config not set, old config set", + config: oldPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: true, + }, + { + description: "New config set, old config not set", + config: newPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: true, + }, + { + description: "New config and old config set", + config: oldAndNewPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: false, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigPurposeOneTreatment(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.wantPurpose1TreatmentEnabled, v.Get("gdpr.tcf2.purpose_one_treatment.enabled").(bool), tt.description) + assert.Equal(t, tt.wantPurpose1TreatmentAccessAllowed, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed").(bool), tt.description) + } else { + assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed"), tt.description) + } + } +} + func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -569,12 +644,6 @@ func TestInvalidHostVendorID(t *testing.T) { } } -func TestInvalidFetchGVL(t *testing.T) { - cfg := newDefaultConfig(t) - cfg.GDPR.TCF1.FetchGVL = true - assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward") -} - func TestInvalidAMPException(t *testing.T) { cfg := newDefaultConfig(t) cfg.GDPR.AMPException = true diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 50636d35ccd..1788d508c31 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1447,8 +1447,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } } -func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { - tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" +func TestCleanOpenRTBRequestsGDPR(t *testing.T) { tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" trueValue, falseValue := true, false @@ -1477,19 +1476,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf1Consent, - gdprScrub: true, - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }, - }, - { - description: "Enforce - TCF 2", + description: "Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "1", @@ -1501,11 +1488,11 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Not Enforce - TCF 1", + description: "Not Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1513,36 +1500,36 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1; GDPR signal extraction error", + description: "Enforce; GDPR signal extraction error", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0{", - gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, expectError: true, }, { - description: "Enforce - TCF 1; account GDPR enabled, host GDPR setting disregarded", + description: "Enforce; account GDPR enabled, host GDPR setting disregarded", gdprAccountEnabled: &trueValue, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR disabled, host GDPR setting disregarded", + description: "Not Enforce; account GDPR disabled, host GDPR setting disregarded", gdprAccountEnabled: &falseValue, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1550,23 +1537,23 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1; account GDPR not specified, host GDPR enabled", + description: "Enforce; account GDPR not specified, host GDPR enabled", gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR not specified, host GDPR disabled", + description: "Not Enforce; account GDPR not specified, host GDPR disabled", gdprAccountEnabled: nil, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1578,12 +1565,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, userSyncIfAmbiguous: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { @@ -1591,7 +1578,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, userSyncIfAmbiguous: true, expectPrivacyLabels: metrics.PrivacyLabels{ @@ -1604,12 +1591,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index ffd5ced462a..4179d8122ac 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -30,7 +30,6 @@ type Permissions interface { // Versions of the GDPR TCF technical specification. const ( - tcf1SpecVersion uint8 = 1 tcf2SpecVersion uint8 = 2 ) @@ -44,8 +43,7 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ cfg: cfg, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: newVendorListFetcherTCF1(cfg), - tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)}, + tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } if cfg.HostVendorID == 0 { diff --git a/gdpr/impl.go b/gdpr/impl.go index a91a9308e24..aca07fd068d 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/prebid/go-gdpr/api" - tcf1constants "github.com/prebid/go-gdpr/consentconstants" - consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" + "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" @@ -116,23 +116,15 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } - // InfoStorageAccess is the same across TCF 1 and TCF 2 - if parsedConsent.Version() == 2 { - if !p.cfg.TCF2.Purpose1.Enabled { - // We are not enforcing purpose 1 - return true, nil - } - consent, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err := fmt.Errorf("Unable to access TCF2 parsed consent") - return false, err - } - return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil - } - if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { + if !p.cfg.TCF2.Purpose1.Enabled { return true, nil } - return false, nil + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + err := fmt.Errorf("Unable to access TCF2 parsed consent") + return false, err + } + return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, false), nil } func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { @@ -141,44 +133,33 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, return false, false, false, err } + // vendor will be nil if not a valid TCF2 consent string if vendor == nil { return false, false, false, nil } - if parsedConsent.Version() == 2 { - if p.cfg.TCF2.Enabled { - return p.allowActivitiesTCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) - } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { - return true, true, true, nil - } - } else { - if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, true, true, nil - } + if !p.cfg.TCF2.Enabled { + return true, false, false, nil } - return true, false, false, nil -} -func (p *permissionsImpl) allowActivitiesTCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { - consent, ok := parsedConsent.(tcf2.ConsentMetadata) + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return } if p.cfg.TCF2.SpecialPurpose1.Enabled { - passGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + passGeo = consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) } else { passGeo = true } if p.cfg.TCF2.Purpose2.Enabled { - allowBidRequest = p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(2), weakVendorEnforcement) + allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), weakVendorEnforcement) } else { allowBidRequest = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), weakVendorEnforcement) { passID = true break } @@ -191,8 +172,8 @@ const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool { - if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { @@ -224,7 +205,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons } version := parsedConsent.Version() - if version < 1 || version > 2 { + if version != 2 { return } vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 3b974ffa3bb..d26c0c57231 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -23,7 +23,6 @@ func TestDisallowOnEmptyConsent(t *testing.T) { }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: failedListFetcher, tcf2SpecVersion: failedListFetcher, }, } @@ -49,106 +48,120 @@ func TestAllowOnSignalNo(t *testing.T) { } func TestAllowedSyncs(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, - {ID: 3, Purposes: []int{1}}, + vendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABCAAIAAAAAAAAAAACEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) } func TestProhibitedPurposes(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + vendor2NoPurpose1Consent := "CPGWkCaPGWkCaApAAAENABCAAAAAAAAAAAAAABEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } func TestProhibitedVendors(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + purpose1NoVendorConsent := "CPGWkCaPGWkCaApAAAENABCAAIAAAAAAAAAAABAAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -159,7 +172,6 @@ func TestMalformedConsent(t *testing.T) { HostVendorID: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(nil), tcf2SpecVersion: listFetcher(nil), }, } @@ -172,7 +184,7 @@ func TestMalformedConsent(t *testing.T) { func TestAllowActivities(t *testing.T) { bidderAllowedByConsent := openrtb_ext.BidderAppnexus bidderBlockedByConsent := openrtb_ext.BidderRubicon - consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + vendor2AndPurpose2Consent := "CPGWbY_PGWbY_GYAAAENABCAAEAAAAAAAAAAACEAAAAA" tests := []struct { description string @@ -190,7 +202,7 @@ func TestAllowActivities(t *testing.T) { publisherID: "appNexusAppID", userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -198,7 +210,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalNo, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -206,7 +218,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -222,7 +234,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: true, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -238,7 +250,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -254,31 +266,37 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: false, }, } - - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + vendorListData := MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1, 3}}, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{2}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}}, + TCF2: config.TCF2{ + Enabled: true, + Purpose2: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } @@ -293,10 +311,10 @@ func TestAllowActivities(t *testing.T) { } } -func buildTCF2VendorList34() tcf2VendorList { - return tcf2VendorList{ +func buildVendorList34() vendorList { + return vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{ + Vendors: map[string]*vendor{ "2": { ID: 2, Purposes: []int{1}, @@ -332,7 +350,7 @@ func buildTCF2VendorList34() tcf2VendorList { } } -var tcf2Config = config.GDPR{ +var gdprConfig = config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, @@ -343,7 +361,7 @@ var tcf2Config = config.GDPR{ }, } -type tcf2TestDef struct { +type testDef struct { description string bidder openrtb_ext.BidderName consent string @@ -353,10 +371,10 @@ type tcf2TestDef struct { weakVendorEnforcement bool } -func TestAllowActivitiesTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesGeoAndID(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -364,7 +382,6 @@ func TestAllowActivitiesTCF2(t *testing.T) { openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), 74: parseVendorListDataV2(t, vendorListData), @@ -372,8 +389,8 @@ func TestAllowActivitiesTCF2(t *testing.T) { }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 - testDefs := []tcf2TestDef{ + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, @@ -429,17 +446,16 @@ func TestAllowActivitiesTCF2(t *testing.T) { } } -func TestAllowActivitiesWhitelistTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesWhitelist(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -453,17 +469,16 @@ func TestAllowActivitiesWhitelistTCF2(t *testing.T) { assert.EqualValuesf(t, true, passID, "PassID failure") } -func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesPubRestrict(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 32, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 15: parseVendorListDataV2(t, vendorListData), }), @@ -472,7 +487,7 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, // Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent - testDefs := []tcf2TestDef{ + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, @@ -504,24 +519,23 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { } } -func TestAllowSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") @@ -531,19 +545,18 @@ func TestAllowSyncTCF2(t *testing.T) { assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedPurposeSyncTCF2(t *testing.T) { - tcf2VendorList34 := buildTCF2VendorList34() - tcf2VendorList34.Vendors["8"].Purposes = []int{7} - vendorListData := tcf2MarshalVendorList(tcf2VendorList34) +func TestProhibitedPurposeSync(t *testing.T) { + vendorList34 := buildVendorList34() + vendorList34.Vendors["8"].Purposes = []int{7} + vendorListData := MarshalVendorList(vendorList34) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -551,7 +564,7 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 8 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -561,10 +574,10 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedVendorSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestProhibitedVendorSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -572,7 +585,6 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { openrtb_ext.BidderOpenx: 10, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -580,7 +592,7 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 10 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -713,7 +725,7 @@ func TestNormalizeGDPR(t *testing.T) { } } -func TestAllowActivitiesTCF2BidRequests(t *testing.T) { +func TestAllowActivitiesBidRequests(t *testing.T) { purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" @@ -757,7 +769,7 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { } for _, td := range testDefs { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, @@ -773,7 +785,6 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { openrtb_ext.BidderPubmatic: 6, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -787,3 +798,21 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } + +func TestTCF1Consent(t *testing.T) { + bidderAllowedByConsent := openrtb_ext.BidderAppnexus + tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + + perms := permissionsImpl{ + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + }, + } + + bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, "", SignalYes, tcf1Consent, false) + + assert.Nil(t, err, "TCF1 consent - no error returned") + assert.Equal(t, false, bidReq, "TCF1 consent - bid request not allowed") + assert.Equal(t, false, passGeo, "TCF1 consent - passing geo not allowed") + assert.Equal(t, false, passID, "TCF1 consent - passing id not allowed") +} diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index bc7eab40647..24489e73265 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -26,33 +26,7 @@ type saveVendors func(uint16, api.VendorList) // // Nothing in this file is exported. Public APIs can be found in gdpr.go -func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - if len(cfg.TCF1.FallbackGVLPath) == 0 { - return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { - return nil, makeVendorListNotFoundError(vendorListVersion) - } - } - - fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath) - return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) { - return fallback, nil - } -} - -func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList { - fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) - if err != nil { - glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) - } - - fallback, err := vendorlist.ParseEagerly(fallbackContents) - if err != nil { - glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) - } - return fallback -} - -func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { +func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { cacheSave, cacheLoad := newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 27f1bc3b996..95529e4e334 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -14,71 +14,15 @@ import ( "github.com/prebid/prebid-server/config" ) -func TestTCF1FetcherInitialLoad(t *testing.T) { - // Loads two vendor lists during initialization by setting the latest vendor list version to 2. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 2, - vendorLists: map[int]string{ - 1: tcf1VendorList1, - 2: tcf1VendorList2, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorListFallbackExpected, - }, - { - description: "Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - } - - for _, test := range testCases { - runTestTCF1(t, test, server) - } -} - -func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { +func TestFetcherDynamicLoadListExists(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor lists will be dynamically loaded. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, - 2: tcf2VendorList2, + 1: vendorList1, + 2: vendorList2, }, }))) defer server.Close() @@ -91,17 +35,17 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { expected: vendorList2Expected, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { +func TestFetcherDynamicLoadListDoesntExist(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor list load attempts will be done dynamically. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, + 1: vendorList1, }, }))) defer server.Close() @@ -116,30 +60,30 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { }, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherThrottling(t *testing.T) { +func TestFetcherThrottling(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2MarshalVendorList(tcf2VendorList{ + 1: MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1}}}, }), - 2: tcf2MarshalVendorList(tcf2VendorList{ + 2: MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, }), - 3: tcf2MarshalVendorList(tcf2VendorList{ + 3: MarshalVendorList(vendorList{ VendorListVersion: 3, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, }), }, }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) // Dynamically Load List 2 Successfully _, errList1 := fetcher(context.Background(), 2) @@ -151,7 +95,7 @@ func TestTCF2FetcherThrottling(t *testing.T) { assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2MalformedVendorlist(t *testing.T) { +func TestMalformedVendorlist(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ @@ -160,30 +104,30 @@ func TestTCF2MalformedVendorlist(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) // Fetching should fail since vendor list could not be unmarshalled. assert.Error(t, err) } -func TestTCF2ServerUrlInvalid(t *testing.T) { +func TestServerUrlInvalid(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" } - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2ServerUnavailable(t *testing.T) { +func TestServerUnavailable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") @@ -213,24 +157,14 @@ func TestVendorListURLMaker(t *testing.T) { } } -var tcf1VendorList1 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList1 = MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2}}}, }) -var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}}, -}) - -var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList2 = MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}}, -}) - -var tcf2VendorList2 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, }) var vendorList2Expected = testExpected{ @@ -245,27 +179,12 @@ var vendorListFallbackExpected = testExpected{ vendorPurposes: map[int]bool{1: true, 2: false, 3: true}, } -type tcf1VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors []tcf1Vendor `json:"vendors"` +type vendorList struct { + VendorListVersion uint16 `json:"vendorListVersion"` + Vendors map[string]*vendor `json:"vendors"` } -type tcf1Vendor struct { - ID uint16 `json:"id"` - Purposes []int `json:"purposeIds"` -} - -func tcf1MarshalVendorList(vendorList tcf1VendorList) string { - json, _ := json.Marshal(vendorList) - return string(json) -} - -type tcf2VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors map[string]*tcf2Vendor `json:"vendors"` -} - -type tcf2Vendor struct { +type vendor struct { ID uint16 `json:"id"` Purposes []int `json:"purposes"` LegIntPurposes []int `json:"legIntPurposes"` @@ -273,7 +192,7 @@ type tcf2Vendor struct { SpecialPurposes []int `json:"specialPurposes"` } -func tcf2MarshalVendorList(vendorList tcf2VendorList) string { +func MarshalVendorList(vendorList vendorList) string { json, _ := json.Marshal(vendorList) return string(json) } @@ -323,8 +242,7 @@ type test struct { } type testSetup struct { - enableTCF1Fallback bool - vendorListVersion uint16 + vendorListVersion uint16 } type testExpected struct { @@ -334,31 +252,9 @@ type testExpected struct { vendorPurposes map[int]bool } -func runTestTCF1(t *testing.T, test test, server *httptest.Server) { +func runTest(t *testing.T, test test, server *httptest.Server) { config := testConfig() - if test.setup.enableTCF1Fallback { - config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - } - - fetcher := newVendorListFetcherTCF1(config) - vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) - - if test.expected.errorMessage != "" { - assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") - } else { - assert.NoError(t, err, test.description+":vendorlist") - assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") - vendor := vendorList.Vendor(test.expected.vendorID) - for id, expected := range test.expected.vendorPurposes { - result := vendor.Purpose(consentconstants.Purpose(id)) - assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) - } - } -} - -func runTestTCF2(t *testing.T, test test, server *httptest.Server) { - config := testConfig() - fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server)) vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) if test.expected.errorMessage != "" { @@ -387,8 +283,5 @@ func testConfig() config.GDPR { InitVendorlistFetch: 60 * 1000, ActiveVendorlistFetch: 1000 * 5, }, - TCF1: config.TCF1{ - FetchGVL: true, - }, } } diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 7c0a3f377e2..61930bf54f0 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -60,7 +60,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut) ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest) ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest) - ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1]) ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2]) ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr]) } @@ -548,25 +547,16 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: TCFVersionErr, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) m.RecordRequestPrivacy(PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: TCFVersionV2, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA") assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out") assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA") assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err") - assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") } diff --git a/metrics/metrics.go b/metrics/metrics.go index 7cb5f0f1d88..5912a151bca 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -299,7 +299,6 @@ type TCFVersionValue string const ( TCFVersionErr TCFVersionValue = "err" - TCFVersionV1 TCFVersionValue = "v1" TCFVersionV2 TCFVersionValue = "v2" ) @@ -307,7 +306,6 @@ const ( func TCFVersions() []TCFVersionValue { return []TCFVersionValue{ TCFVersionErr, - TCFVersionV1, TCFVersionV2, } } @@ -315,8 +313,6 @@ func TCFVersions() []TCFVersionValue { // TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue func TCFVersionToValue(version int) TCFVersionValue { switch { - case version == 1: - return TCFVersionV1 case version == 2: return TCFVersionV2 } diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 72ddd105152..087ee570551 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1390,18 +1390,10 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionErr, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) m.RecordRequestPrivacy(metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, float64(1), @@ -1436,13 +1428,6 @@ func TestRecordRequestPrivacy(t *testing.T) { versionLabel: "err", }) - assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF, - float64(2), - prometheus.Labels{ - sourceLabel: sourceRequest, - versionLabel: "v1", - }) - assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF, float64(1), prometheus.Labels{ diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index dc8f56425c5..a0fa6241d72 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -17,11 +17,6 @@ func TestValidateConsent(t *testing.T) { consent: "", expected: false, }, - { - description: "TCF1 Valid", - consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expected: true, - }, { description: "TCF2 Valid", consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json deleted file mode 100644 index 9f1c8506b32..00000000000 --- a/static/tcf1/fallback_gvl.json +++ /dev/null @@ -1 +0,0 @@ -{"vendorListVersion":215,"lastUpdated":"2020-08-13T16:00:19Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Mediakeys Platform","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"},{"id":808,"name":"Pure Local Media GmbH","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://purelocalmedia.de/?page_id=593"}]} \ No newline at end of file From a9ee429449797f9a7fdf48a47c295fd5377cbf85 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:26:55 -0400 Subject: [PATCH 436/603] Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) --- config/config.go | 9 +- config/config_test.go | 13 ++- endpoints/cookie_sync.go | 6 +- endpoints/cookie_sync_test.go | 4 +- exchange/exchange.go | 58 +++++------ exchange/exchange_test.go | 37 ++++--- exchange/targeting_test.go | 18 ++-- exchange/utils.go | 4 +- exchange/utils_test.go | 63 ++++++------ gdpr/impl.go | 2 +- gdpr/impl_test.go | 176 +++++++++++++++++----------------- 11 files changed, 209 insertions(+), 181 deletions(-) diff --git a/config/config.go b/config/config.go index 7e24a00d370..1260d28a779 100644 --- a/config/config.go +++ b/config/config.go @@ -193,7 +193,7 @@ type Privacy struct { type GDPR struct { Enabled bool `mapstructure:"enabled"` HostVendorID int `mapstructure:"host_vendor_id"` - UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` + DefaultValue string `mapstructure:"default_value"` Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]struct{} @@ -202,12 +202,15 @@ type GDPR struct { // EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies. // If the gdpr flag is unset in a request, but geo.country is set, we will assume GDPR applies if and only // if the country matches one on this list. If both the GDPR flag and country are not set, we default - // to UsersyncIfAmbiguous + // to DefaultValue EEACountries []string `mapstructure:"eea_countries"` EEACountriesMap map[string]struct{} } func (cfg *GDPR) validate(errs []error) []error { + if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { + errs = append(errs, fmt.Errorf("gdpr.default_value must be 0 or 1")) + } if cfg.HostVendorID < 0 || cfg.HostVendorID > 0xffff { errs = append(errs, fmt.Errorf("gdpr.host_vendor_id must be in the range [0, %d]. Got %d", 0xffff, cfg.HostVendorID)) } @@ -937,7 +940,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("amp_timeout_adjustment_ms", 0) v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) - v.SetDefault("gdpr.usersync_if_ambiguous", false) + v.SetDefault("gdpr.default_value", "1") v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) diff --git a/config/config_test.go b/config/config_test.go index f34bd5fa189..a2b28d026d7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -148,7 +148,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - usersync_if_ambiguous: true + default_value: "0" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +352,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) - cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true) + cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "0") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -460,6 +460,9 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { func TestValidConfig(t *testing.T) { cfg := Configuration{ + GDPR: GDPR{ + DefaultValue: "1", + }, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ @@ -650,6 +653,12 @@ func TestInvalidAMPException(t *testing.T) { assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") } +func TestInvalidGDPRDefaultValue(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.GDPR.DefaultValue = "2" + assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") +} + func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { cfg := Configuration{ CurrencyConverter: CurrencyConverter{ diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index bf3935f0535..3c1354c86bd 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -97,7 +97,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h } parsedReq := &cookieSyncRequest{} - if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.UsersyncIfAmbiguous); err != nil { + if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.DefaultValue); err != nil { co.Status = http.StatusBadRequest co.Errors = append(co.Errors, err) http.Error(w, co.Errors[len(co.Errors)-1].Error(), co.Status) @@ -181,7 +181,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h enc.Encode(csResp) } -func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbiguous bool) error { +func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, gdprDefaultValue string) error { if err := json.Unmarshal(bodyBytes, parsedReq); err != nil { return fmt.Errorf("JSON parsing failed: %s", err.Error()) } @@ -193,7 +193,7 @@ func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbi if parsedReq.GDPR == nil { var gdpr = new(int) *gdpr = 1 - if usersyncIfAmbiguous { + if gdprDefaultValue == "0" { *gdpr = 0 } parsedReq.GDPR = gdpr diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 7632894baf6..2f56c262979 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -110,7 +110,7 @@ func TestCCPA(t *testing.T) { } for _, test := range testCases { - gdpr := config.GDPR{UsersyncIfAmbiguous: true} + gdpr := config.GDPR{DefaultValue: "0"} ccpa := config.CCPA{Enforce: test.enforceCCPA} rr := doConfigurablePost(test.requestBody, nil, true, syncersForTest(), gdpr, ccpa) assert.Equal(t, http.StatusOK, rr.Code, test.description+":httpResponseCode") @@ -149,7 +149,7 @@ func TestCookieSyncNoBidders(t *testing.T) { } func TestCookieSyncNoCookiesBrokenGDPR(t *testing.T) { - rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{UsersyncIfAmbiguous: true}, config.CCPA{}) + rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{DefaultValue: "0"}, config.CCPA{}) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork"}, parseSyncs(t, rr.Body.Bytes())) diff --git a/exchange/exchange.go b/exchange/exchange.go index 6f0c610a958..7b156b5dee2 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -51,18 +51,18 @@ type IdFetcher interface { } type exchange struct { - adapterMap map[openrtb_ext.BidderName]adaptedBidder - bidderInfo config.BidderInfos - me metrics.MetricsEngine - cache prebid_cache_client.Client - cacheTime time.Duration - gDPR gdpr.Permissions - currencyConverter *currency.RateConverter - externalURL string - UsersyncIfAmbiguous bool - privacyConfig config.Privacy - categoriesFetcher stored_requests.CategoryFetcher - bidIDGenerator BidIDGenerator + adapterMap map[openrtb_ext.BidderName]adaptedBidder + bidderInfo config.BidderInfos + me metrics.MetricsEngine + cache prebid_cache_client.Client + cacheTime time.Duration + gDPR gdpr.Permissions + currencyConverter *currency.RateConverter + externalURL string + gdprDefaultValue string + privacyConfig config.Privacy + categoriesFetcher stored_requests.CategoryFetcher + bidIDGenerator BidIDGenerator } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -111,16 +111,16 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { return &exchange{ - adapterMap: adapters, - bidderInfo: infos, - cache: cache, - cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, - categoriesFetcher: categoriesFetcher, - currencyConverter: currencyConverter, - externalURL: cfg.ExternalURL, - gDPR: gDPR, - me: metricsEngine, - UsersyncIfAmbiguous: cfg.GDPR.UsersyncIfAmbiguous, + adapterMap: adapters, + bidderInfo: infos, + cache: cache, + cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, + categoriesFetcher: categoriesFetcher, + currencyConverter: currencyConverter, + externalURL: cfg.ExternalURL, + gDPR: gDPR, + me: metricsEngine, + gdprDefaultValue: cfg.GDPR.DefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -186,10 +186,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * recordImpMetrics(r.BidRequest, e.me) // Make our best guess if GDPR applies - usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) + gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, usersyncIfAmbiguous, e.privacyConfig, &r.Account) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) @@ -301,8 +301,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool { - usersyncIfAmbiguous := e.UsersyncIfAmbiguous +func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) string { + gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { @@ -314,14 +314,14 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) boo // If we have a country set, and it is on the list, we assume GDPR applies if not set on the request. // Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long). if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found { - usersyncIfAmbiguous = false + gdprDefaultValue = "1" } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - usersyncIfAmbiguous = true + gdprDefaultValue = "0" } } - return usersyncIfAmbiguous + return gdprDefaultValue } func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index cd36ad94b22..5a54b5e14d9 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2004,6 +2004,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { eeac[c] = s } + var gdprDefaultValue string + if spec.AssumeGDPRApplies { + gdprDefaultValue = "1" + } else { + gdprDefaultValue = "0" + } + privacyConfig := config.Privacy{ CCPA: config.CCPA{ Enforce: spec.EnforceCCPA, @@ -2012,9 +2019,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Enforce: spec.EnforceLMT, }, GDPR: config.GDPR{ - Enabled: spec.GDPREnabled, - UsersyncIfAmbiguous: !spec.AssumeGDPRApplies, - EEACountriesMap: eeac, + Enabled: spec.GDPREnabled, + DefaultValue: gdprDefaultValue, + EEACountriesMap: eeac, }, } bidIdGenerator := &mockBidIDGenerator{} @@ -2156,18 +2163,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } return &exchange{ - adapterMap: bidderAdapters, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), - cache: &wellBehavedCache{}, - cacheTime: 0, - gDPR: &permissionsMock{allowAllBidders: true}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous, - privacyConfig: privacyConfig, - categoriesFetcher: categoriesFetcher, - bidderInfo: bidderInfos, - externalURL: "http://localhost", - bidIDGenerator: bidIDGenerator, + adapterMap: bidderAdapters, + me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), + cache: &wellBehavedCache{}, + cacheTime: 0, + gDPR: &permissionsMock{allowAllBidders: true}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: privacyConfig.GDPR.DefaultValue, + privacyConfig: privacyConfig, + categoriesFetcher: categoriesFetcher, + bidderInfo: bidderInfos, + externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, } } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index aa07ed0c77b..86646957091 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -87,15 +87,15 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op } ex := &exchange{ - adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.DummyMetricsEngine{}, - cache: &wellBehavedCache{}, - cacheTime: time.Duration(0), - gDPR: gdpr.AlwaysAllow{}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: false, - categoriesFetcher: categoriesFetcher, - bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), + me: &metricsConf.DummyMetricsEngine{}, + cache: &wellBehavedCache{}, + cacheTime: time.Duration(0), + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: "1", + categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) diff --git a/exchange/utils.go b/exchange/utils.go index 3d5e2374008..c5cf673250d 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - usersyncIfAmbiguous bool, + gdprDefaultValue string, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && !usersyncIfAmbiguous) + gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == "1") ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1788d508c31..dad0d69db15 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, true, privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - true, + "0", privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, true, privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1459,7 +1459,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent string gdprScrub bool permissionsError error - userSyncIfAmbiguous bool + gdprDefaultValue string expectPrivacyLabels metrics.PrivacyLabels expectError bool }{ @@ -1470,6 +1470,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: "malformed", gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", @@ -1482,6 +1483,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1494,6 +1496,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1506,6 +1509,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0{", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1519,6 +1523,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1531,6 +1536,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1543,6 +1549,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1555,32 +1562,33 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce - Ambiguous signal, don't sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: true, - userSyncIfAmbiguous: false, + description: "Enforce - Ambiguous signal, don't sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - Ambiguous signal, sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: false, - userSyncIfAmbiguous: true, + description: "Not Enforce - Ambiguous signal, sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprDefaultValue: "0", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1594,6 +1602,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1610,8 +1619,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprHostEnabled, - UsersyncIfAmbiguous: test.userSyncIfAmbiguous, + Enabled: test.gdprHostEnabled, + DefaultValue: test.gdprDefaultValue, TCF2: config.TCF2{ Enabled: true, }, @@ -1636,7 +1645,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.userSyncIfAmbiguous, + test.gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1698,8 +1707,8 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprEnforced, - UsersyncIfAmbiguous: true, + Enabled: test.gdprEnforced, + DefaultValue: "0", TCF2: config.TCF2{ Enabled: true, }, @@ -1727,7 +1736,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - true, + "0", privacyConfig, nil) diff --git a/gdpr/impl.go b/gdpr/impl.go index aca07fd068d..9c09e90b58e 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -94,7 +94,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.UsersyncIfAmbiguous { + if p.cfg.DefaultValue == "0" { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d26c0c57231..345dd52621d 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -18,8 +18,8 @@ import ( func TestDisallowOnEmptyConsent(t *testing.T) { perms := permissionsImpl{ cfg: config.GDPR{ - HostVendorID: 3, - UsersyncIfAmbiguous: true, + HostVendorID: 3, + DefaultValue: "0", }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ @@ -190,84 +190,84 @@ func TestAllowActivities(t *testing.T) { description string bidderName openrtb_ext.BidderName publisherID string - userSyncIfAmbiguous bool + gdprDefaultValue string gdpr Signal consent string passID bool weakVendorEnforcement bool }{ { - description: "Allow PI - Non standard publisher", - bidderName: bidderBlockedByConsent, - publisherID: "appNexusAppID", - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - Non standard publisher", + bidderName: bidderBlockedByConsent, + publisherID: "appNexusAppID", + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with No GDPR", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalNo, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - known vendor with No GDPR", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalNo, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with Yes GDPR", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - known vendor with Yes GDPR", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: "", - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: "", + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: "", - passID: false, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: "", + passID: false, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Don't allow PI - known vendor with Yes GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: "", - passID: false, + description: "Don't allow PI - known vendor with Yes GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: "", + passID: false, }, { - description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: false, + description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: false, }, } vendorListData := MarshalVendorList(vendorList{ @@ -302,7 +302,7 @@ func TestAllowActivities(t *testing.T) { } for _, tt := range tests { - perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous + perms.cfg.DefaultValue = tt.gdprDefaultValue _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -669,53 +669,53 @@ func assertStringsEqual(t *testing.T, expected string, actual string) { func TestNormalizeGDPR(t *testing.T) { tests := []struct { - description string - userSyncIfAmbiguous bool - giveSignal Signal - wantSignal Signal + description string + gdprDefaultValue string + giveSignal Signal + wantSignal Signal }{ { - description: "Don't normalize - Signal No and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal No and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalAmbiguous, - wantSignal: SignalYes, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalAmbiguous, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalAmbiguous, - wantSignal: SignalNo, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalAmbiguous, + wantSignal: SignalNo, }, } for _, tt := range tests { perms := permissionsImpl{ cfg: config.GDPR{ - UsersyncIfAmbiguous: tt.userSyncIfAmbiguous, + DefaultValue: tt.gdprDefaultValue, }, } From 037bd7f19920e01ee2ee2504410b9b84f134bf92 Mon Sep 17 00:00:00 2001 From: Rachel Joyce Date: Tue, 15 Jun 2021 10:19:30 -0600 Subject: [PATCH 437/603] Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) --- adapters/sovrn/sovrn.go | 5 +- .../both-custom-default-bidfloor.json | 126 ++++++++++++++++++ .../sovrntest/supplemental/no-bidfloor.json | 122 +++++++++++++++++ .../supplemental/only-custom-bidfloor.json | 125 +++++++++++++++++ .../supplemental/only-default-bidfloor.json | 124 +++++++++++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/no-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index be1c2221ae5..40969d3638e 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -287,7 +287,10 @@ func preprocess(imp *openrtb2.Imp) (string, error) { } imp.TagID = getTagid(sovrnExt) - imp.BidFloor = sovrnExt.BidFloor + + if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 { + imp.BidFloor = sovrnExt.BidFloor + } return imp.TagID, nil } diff --git a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json new file mode 100644 index 00000000000..4b997b68266 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json new file mode 100644 index 00000000000..0aa3ad74e62 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json new file mode 100644 index 00000000000..3cd6539f988 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 4.20, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json new file mode 100644 index 00000000000..cb74e5643b6 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} From f894d18ee447ab35f7822295c207ec24cf0a1086 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 16 Jun 2021 15:23:16 -0400 Subject: [PATCH 438/603] GDPR: require host specify default value (#1859) --- config/config.go | 14 ++++--- config/config_test.go | 76 +++++++++++++++++++++++--------------- endpoints/auction_test.go | 1 + exchange/exchange.go | 15 +++++--- exchange/exchange_test.go | 7 +++- exchange/targeting_test.go | 2 +- exchange/utils.go | 4 +- exchange/utils_test.go | 21 +++++++---- gdpr/gdpr.go | 10 ++++- gdpr/impl.go | 9 +++-- gdpr/impl_test.go | 14 ++++++- 11 files changed, 113 insertions(+), 60 deletions(-) diff --git a/config/config.go b/config/config.go index 1260d28a779..5621c2ec416 100644 --- a/config/config.go +++ b/config/config.go @@ -93,7 +93,7 @@ type HTTPClient struct { IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` } -func (cfg *Configuration) validate() []error { +func (cfg *Configuration) validate(v *viper.Viper) []error { var errs []error errs = cfg.AuctionTimeouts.validate(errs) errs = cfg.StoredRequests.validate(errs) @@ -105,7 +105,7 @@ func (cfg *Configuration) validate() []error { if cfg.MaxRequestSize < 0 { errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize)) } - errs = cfg.GDPR.validate(errs) + errs = cfg.GDPR.validate(v, errs) errs = cfg.CurrencyConverter.validate(errs) errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) @@ -207,8 +207,10 @@ type GDPR struct { EEACountriesMap map[string]struct{} } -func (cfg *GDPR) validate(errs []error) []error { - if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { +func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error { + if !v.IsSet("gdpr.default_value") { + errs = append(errs, fmt.Errorf("gdpr.default_value is required and must be specified")) + } else if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { errs = append(errs, fmt.Errorf("gdpr.default_value must be 0 or 1")) } if cfg.HostVendorID < 0 || cfg.HostVendorID > 0xffff { @@ -520,7 +522,7 @@ func New(v *viper.Viper) (*Configuration, error) { glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") - if errs := c.validate(); len(errs) > 0 { + if errs := c.validate(v); len(errs) > 0 { return &c, errortypes.NewAggregateError("validation errors", errs) } @@ -938,9 +940,9 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("analytics.pubstack.buffers.count", 100) v.SetDefault("analytics.pubstack.buffers.timeout", "900s") v.SetDefault("amp_timeout_adjustment_ms", 0) + v.BindEnv("gdpr.default_value") v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) - v.SetDefault("gdpr.default_value", "1") v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) diff --git a/config/config_test.go b/config/config_test.go index a2b28d026d7..fa9dcdc5195 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -116,10 +116,7 @@ func TestExternalCacheURLValidate(t *testing.T) { } func TestDefaults(t *testing.T) { - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + cfg, _ := newDefaultConfig(t) cmpInts(t, "port", cfg.Port, 8000) cmpInts(t, "admin_port", cfg.AdminPort, 6060) @@ -148,7 +145,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - default_value: "0" + default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +349,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) - cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "0") + cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "1") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -429,6 +426,7 @@ func TestFullConfig(t *testing.T) { func TestUnmarshalAdapterExtraInfo(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(adapterExtraInfoConfig)) cfg, err := New(v) @@ -484,14 +482,18 @@ func TestValidConfig(t *testing.T) { }, } + v := viper.New() + v.Set("gdpr.default_value", "0") + resolvedStoredRequestsConfig(&cfg) - err := cfg.validate() + err := cfg.validate(v) assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } func TestMigrateConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) migrateConfig(v) @@ -508,10 +510,7 @@ func TestMigrateConfigFromEnv(t *testing.T) { defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") } os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + cfg, _ := newDefaultConfig(t) cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) } @@ -591,6 +590,7 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidAdapterEndpointConfig)) _, err := New(v) @@ -600,6 +600,7 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) { func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidUserSyncURLConfig)) _, err := New(v) @@ -607,16 +608,16 @@ func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { } func TestNegativeRequestSize(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 - assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1") + assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1") } func TestNegativePrometheusTimeout(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Metrics.Prometheus.Port = 8001 cfg.Metrics.Prometheus.TimeoutMillisRaw = 0 - assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") + assertOneError(t, cfg.validate(v), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") } func TestInvalidHostVendorID(t *testing.T) { @@ -638,9 +639,9 @@ func TestInvalidHostVendorID(t *testing.T) { } for _, tt := range tests { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.HostVendorID = tt.vendorID - errs := cfg.validate() + errs := cfg.validate(v) assert.Equal(t, 1, len(errs), tt.description) assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) @@ -648,34 +649,47 @@ func TestInvalidHostVendorID(t *testing.T) { } func TestInvalidAMPException(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.AMPException = true - assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") + assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") } func TestInvalidGDPRDefaultValue(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.DefaultValue = "2" - assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") + assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1") +} + +func TestMissingGDPRDefaultValue(t *testing.T) { + v := viper.New() + + cfg, _ := newDefaultConfig(t) + assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") } func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: -1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds should prevent negative values, but it doesn't") } func TestOverflowedCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: (0xffff) + 1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff) } @@ -733,6 +747,7 @@ func TestNewCallsRequestValidation(t *testing.T) { for _, test := range testCases { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer([]byte( `request_validation: @@ -750,31 +765,32 @@ func TestNewCallsRequestValidation(t *testing.T) { } func TestValidateDebug(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Debug.TimeoutNotification.SamplingRate = 1.1 - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed") } func TestValidateAccountsConfigRestrictions(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Accounts.Files.Enabled = true cfg.Accounts.HTTP.Endpoint = "http://localhost" cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts" - errs := cfg.validate() + errs := cfg.validate(v) assert.Len(t, errs, 1) assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files")) } -func newDefaultConfig(t *testing.T) *Configuration { +func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") cfg, err := New(v) - assert.NoError(t, err) - return cfg + assert.NoError(t, err, "Setting up config should work but it doesn't") + return cfg, v } func assertOneError(t *testing.T, errs []error, message string) { diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 2062589e895..bdf68db5be7 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -346,6 +346,7 @@ func TestCacheVideoOnly(t *testing.T) { ctx := context.TODO() v := viper.New() config.SetupViper(v, "") + v.Set("gdpr.default_value", "0") cfg, err := config.New(v) if err != nil { t.Fatal(err.Error()) diff --git a/exchange/exchange.go b/exchange/exchange.go index 7b156b5dee2..e96aadbce86 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -59,7 +59,7 @@ type exchange struct { gDPR gdpr.Permissions currencyConverter *currency.RateConverter externalURL string - gdprDefaultValue string + gdprDefaultValue gdpr.Signal privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher bidIDGenerator BidIDGenerator @@ -110,6 +110,11 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { } func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { + gdprDefaultValue := gdpr.SignalYes + if cfg.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ adapterMap: adapters, bidderInfo: infos, @@ -120,7 +125,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid externalURL: cfg.ExternalURL, gDPR: gDPR, me: metricsEngine, - gdprDefaultValue: cfg.GDPR.DefaultValue, + gdprDefaultValue: gdprDefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -301,7 +306,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) string { +func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal { gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil @@ -314,10 +319,10 @@ func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) string // If we have a country set, and it is on the list, we assume GDPR applies if not set on the request. // Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long). if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found { - gdprDefaultValue = "1" + gdprDefaultValue = gdpr.SignalYes } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - gdprDefaultValue = "0" + gdprDefaultValue = gdpr.SignalNo } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 5a54b5e14d9..2d1306af093 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2162,6 +2162,11 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] t.Fatalf("Failed to create a category Fetcher: %v", error) } + gdprDefaultValue := gdpr.SignalYes + if privacyConfig.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ adapterMap: bidderAdapters, me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), @@ -2169,7 +2174,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] cacheTime: 0, gDPR: &permissionsMock{allowAllBidders: true}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: privacyConfig.GDPR.DefaultValue, + gdprDefaultValue: gdprDefaultValue, privacyConfig: privacyConfig, categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 86646957091..f38a6c0266c 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -93,7 +93,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: "1", + gdprDefaultValue: gdpr.SignalYes, categoriesFetcher: categoriesFetcher, bidIDGenerator: &mockBidIDGenerator{false, false}, } diff --git a/exchange/utils.go b/exchange/utils.go index c5cf673250d..3def0425819 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - gdprDefaultValue string, + gdprDefaultValue gdpr.Signal, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == "1") + gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes) ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index dad0d69db15..5a281f9a360 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - "0", + gdpr.SignalNo, privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1639,13 +1639,18 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { Account: accountConfig, } + gdprDefaultValue := gdpr.SignalYes + if test.gdprDefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + results, privacyLabels, errs := cleanOpenRTBRequests( context.Background(), auctionReq, nil, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.gdprDefaultValue, + gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1736,7 +1741,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - "0", + gdpr.SignalNo, privacyConfig, nil) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 4179d8122ac..47d20a50899 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -39,9 +39,15 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ return &AlwaysAllow{} } + gdprDefaultValue := SignalYes + if cfg.DefaultValue == "0" { + gdprDefaultValue = SignalNo + } + permissionsImpl := &permissionsImpl{ - cfg: cfg, - vendorIDs: vendorIDs, + cfg: cfg, + gdprDefaultValue: gdprDefaultValue, + vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } diff --git a/gdpr/impl.go b/gdpr/impl.go index 9c09e90b58e..af7150a8755 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -28,9 +28,10 @@ const ( ) type permissionsImpl struct { - cfg config.GDPR - vendorIDs map[openrtb_ext.BidderName]uint16 - fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) + cfg config.GDPR + gdprDefaultValue Signal + vendorIDs map[openrtb_ext.BidderName]uint16 + fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { @@ -94,7 +95,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.DefaultValue == "0" { + if p.gdprDefaultValue == SignalNo { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 345dd52621d..f4b0392876b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -21,7 +21,8 @@ func TestDisallowOnEmptyConsent(t *testing.T) { HostVendorID: 3, DefaultValue: "0", }, - vendorIDs: nil, + gdprDefaultValue: SignalNo, + vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: failedListFetcher, }, @@ -303,6 +304,11 @@ func TestAllowActivities(t *testing.T) { for _, tt := range tests { perms.cfg.DefaultValue = tt.gdprDefaultValue + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -719,6 +725,12 @@ func TestNormalizeGDPR(t *testing.T) { }, } + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } + normalizedSignal := perms.normalizeGDPR(tt.giveSignal) assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) From ad3e22f0b194dc970b24f5c0616b96e3dcd092a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:33:10 +0200 Subject: [PATCH 439/603] New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 --- adapters/smilewanted/params_test.go | 58 ++++++++++ adapters/smilewanted/smilewanted.go | 106 ++++++++++++++++++ adapters/smilewanted/smilewanted_test.go | 20 ++++ .../exemplary/simple-banner.json | 94 ++++++++++++++++ .../exemplary/simple-video.json | 87 ++++++++++++++ .../smilewantedtest/params/race/banner.json | 3 + .../smilewantedtest/params/race/video.json | 3 + .../supplemental/bad-server-response.json | 63 +++++++++++ .../supplemental/status-code-204.json | 59 ++++++++++ .../supplemental/status-code-400.json | 64 +++++++++++ .../supplemental/unexpected-status-code.json | 64 +++++++++++ adapters/smilewanted/usersync.go | 12 ++ adapters/smilewanted/usersync_test.go | 34 ++++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/smilewanted.yaml | 12 ++ static/bidder-params/smilewanted.json | 14 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 20 files changed, 702 insertions(+) create mode 100644 adapters/smilewanted/params_test.go create mode 100644 adapters/smilewanted/smilewanted.go create mode 100644 adapters/smilewanted/smilewanted_test.go create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-video.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/banner.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/video.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json create mode 100644 adapters/smilewanted/usersync.go create mode 100644 adapters/smilewanted/usersync_test.go create mode 100644 static/bidder-info/smilewanted.yaml create mode 100644 static/bidder-params/smilewanted.json diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go new file mode 100644 index 00000000000..2ea032d6ff3 --- /dev/null +++ b/adapters/smilewanted/params_test.go @@ -0,0 +1,58 @@ +package smilewanted + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/smilewanted.json +// +// These also validate the format of the external API: request.imp[i].ext.smilewanted + +// TestValidParams makes sure that the smilewanted schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmileWanted, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected SmileWanted params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the SmileWanted schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmileWanted, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneId": "zone_code"}`, +} + +var invalidParams = []string{ + `{"zoneId": 100}`, + `{"zoneId": true}`, + `{"zoneId": 123}`, + `{"zoneID": "1"}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go new file mode 100644 index 00000000000..376389df787 --- /dev/null +++ b/adapters/smilewanted/smilewanted.go @@ -0,0 +1,106 @@ +package smilewanted + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + URI string +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + request.AT = 1 //Defaulting to first price auction for all prebid requests + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Json not encoded. err: %s", err), + }} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + headers.Add("sw-integration-type", "prebid_server") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }}, []error{} +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %s.", err), + }} + } + + var bidReq openrtb2.BidRequest + if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + }) + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for. +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner //default type + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType + } + } + return mediaType +} + +// Builder builds a new instance of the SmileWanted adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go new file mode 100644 index 00000000000..75e7849e750 --- /dev/null +++ b/adapters/smilewanted/smilewanted_test.go @@ -0,0 +1,20 @@ +package smilewanted + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSmileWanted, config.Adapter{ + Endpoint: "http://example.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "smilewantedtest", bidder) +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..0c68d74c588 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "smilewanted", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b3ff9ba9edd --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "zoneId": "zone_code_test_video" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_video" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "smilewanted", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/banner.json b/adapters/smilewanted/smilewantedtest/params/race/banner.json new file mode 100644 index 00000000000..42dddd702a0 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_display" +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/video.json b/adapters/smilewanted/smilewantedtest/params/race/video.json new file mode 100644 index 00000000000..64ac780ecde --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_video" +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..461ad9327a9 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "bad_json" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json new file mode 100644 index 00000000000..0d8a432e26d --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json new file mode 100644 index 00000000000..bdf2caa3c01 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..49a11e3ead3 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/usersync.go b/adapters/smilewanted/usersync.go new file mode 100644 index 00000000000..8f29cb845d8 --- /dev/null +++ b/adapters/smilewanted/usersync.go @@ -0,0 +1,12 @@ +package smilewanted + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmileWantedSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smilewanted", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smilewanted/usersync_test.go b/adapters/smilewanted/usersync_test.go new file mode 100644 index 00000000000..497e5061554 --- /dev/null +++ b/adapters/smilewanted/usersync_test.go @@ -0,0 +1,34 @@ +package smilewanted + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSmileWantedSyncer(t *testing.T) { + syncURL := "//csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSmileWantedSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA", + }, + CCPA: ccpa.Policy{ + Consent: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//csync.smilewanted.com/getuid?source=prebid-server&gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 5621c2ec416..58bb40a592c 100644 --- a/config/config.go +++ b/config/config.go @@ -636,6 +636,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartAdserver, "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartyAds, "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmileWanted, "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -906,6 +907,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.smartyads.endpoint", "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}") + v.SetDefault("adapters.smilewanted.endpoint", "http://prebid-server.smilewanted.com") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index a773d268604..1fdb7c2489d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -94,6 +94,7 @@ import ( "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSmartAdserver: smartadserver.Builder, openrtb_ext.BidderSmartRTB: smartrtb.Builder, openrtb_ext.BidderSmartyAds: smartyads.Builder, + openrtb_ext.BidderSmileWanted: smilewanted.Builder, openrtb_ext.BidderSomoaudience: somoaudience.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ea6c809fd9b..91ce9f0d27f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -166,6 +166,7 @@ const ( BidderSmartAdserver BidderName = "smartadserver" BidderSmartRTB BidderName = "smartrtb" BidderSmartyAds BidderName = "smartyads" + BidderSmileWanted BidderName = "smilewanted" BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" @@ -286,6 +287,7 @@ func CoreBidderNames() []BidderName { BidderSmartAdserver, BidderSmartRTB, BidderSmartyAds, + BidderSmileWanted, BidderSomoaudience, BidderSonobi, BidderSovrn, diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml new file mode 100644 index 00000000000..81b0585bb5e --- /dev/null +++ b/static/bidder-info/smilewanted.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "tech@smilewanted.com" +gvlVendorID: 639 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/smilewanted.json b/static/bidder-params/smilewanted.json new file mode 100644 index 00000000000..be4f9bc142d --- /dev/null +++ b/static/bidder-params/smilewanted.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SmileWanted Adapter Params", + "description": "A schema which validates params accepted by the SmileWanted adapter", + "type": "object", + "properties": { + "zoneId": { + "type": "string", + "description": "An ID which identifies the SmileWanted zone code", + "minLength": 1 + } + }, + "required": ["zoneId"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 88752f4d7d7..169417e07e8 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -73,6 +73,7 @@ import ( "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -174,6 +175,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 2ebd541d015..0cea346e423 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -82,6 +82,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSmartAdserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSmartyAds): syncConfig, + string(openrtb_ext.BidderSmileWanted): syncConfig, string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, From 6a642757a486a1bdee464deaeac924ed444ab520 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 17 Jun 2021 11:36:31 -0400 Subject: [PATCH 440/603] Fix a weak vendor enforcement bug where vendor does not exist (#1890) --- gdpr/impl.go | 26 +++++++++++++++++++++++++- gdpr/impl_test.go | 18 ++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index af7150a8755..94c37e61d96 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -136,7 +136,11 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, // vendor will be nil if not a valid TCF2 consent string if vendor == nil { - return false, false, false, nil + if weakVendorEnforcement && parsedConsent.Version() == 2 { + vendor = vendorTrue{} + } else { + return false, false, false, nil + } } if !p.cfg.TCF2.Enabled { @@ -209,6 +213,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons if version != 2 { return } + vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) if err != nil { return @@ -242,3 +247,22 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { return true, true, true, nil } + +// vendorTrue claims everything. +type vendorTrue struct{} + +func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) { + return true +} diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f4b0392876b..d1a6cad75e8 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,10 +382,11 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAudienceNetwork: 55, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ @@ -414,6 +415,15 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { passID: true, weakVendorEnforcement: true, }, + { + description: "Unknown vendor test, insufficient purposes claimed, basic enforcement", + bidder: openrtb_ext.BidderAudienceNetwork, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowBid: true, + passGeo: true, + passID: true, + weakVendorEnforcement: true, + }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, From 96353641c64537a2b866dd0ea61b43f3957377ea Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Thu, 17 Jun 2021 21:28:22 +0530 Subject: [PATCH 441/603] Pubmatic: Sending GPT slotname in impression extension (#1880) --- adapters/pubmatic/pubmatic.go | 27 ++- .../supplemental/gptSlotNameInImpExt.json | 167 ++++++++++++++++++ .../gptSlotNameInImpExtPbAdslot.json | 163 +++++++++++++++++ 3 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index dc1ee9d0dc4..c2e9fffa0fe 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -56,6 +56,21 @@ type pubmaticBidExt struct { VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } +type ExtImpBidderPubmatic struct { + adapters.ExtImpBidder + Data *ExtData `json:"data,omitempty"` +} + +type ExtData struct { + AdServer *ExtAdServer `json:"adserver"` + PBAdSlot string `json:"pbadslot"` +} + +type ExtAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + const ( INVALID_PARAMS = "Invalid BidParam" MISSING_PUBID = "Missing PubID" @@ -70,6 +85,8 @@ const ( dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" + ImpExtAdUnitKey = "dfp_ad_unit_code" + AdServerGAM = "gam" ) func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { @@ -453,7 +470,7 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er imp.Audio = nil } - var bidderExt adapters.ExtImpBidder + var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return err } @@ -501,6 +518,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } + if bidderExt.Data != nil { + if bidderExt.Data.AdServer != nil && bidderExt.Data.AdServer.Name == AdServerGAM && bidderExt.Data.AdServer.AdSlot != "" { + extMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot + } else if bidderExt.Data.PBAdSlot != "" { + extMap[ImpExtAdUnitKey] = bidderExt.Data.PBAdSlot + } + } + imp.Ext = nil if len(extMap) > 0 { ext, err := json.Marshal(extMap) diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json new file mode 100644 index 00000000000..50f677c8c0b --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -0,0 +1,167 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "adserver": { + "name": "gam", + "adslot": "/1111/home" + }, + "pbadslot": "/2222/home" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/1111/home" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "wrapper": { + "profile": 5123, + "version": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json new file mode 100644 index 00000000000..cc057909a5b --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json @@ -0,0 +1,163 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "pbadslot": "/2222/home" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/2222/home" + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "999" + } + }, + "ext": { + "wrapper": { + "profile": 5123, + "version": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "h": 250, + "w": 300, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "pubmatic.com" + ], + "crid": "29681110", + "w": 300, + "h": 250, + "dealid": "test deal", + "ext": { + "dspid": 6, + "deal_channel": 1 + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file From 4b8c1bd39f9ecf233e474246f33ced1d68279781 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Sat, 19 Jun 2021 16:37:48 -0400 Subject: [PATCH 442/603] Update To Go 1.16 (#1888) --- .devcontainer/devcontainer.json | 4 ++-- .github/workflows/validate-merge.yml | 2 +- .github/workflows/validate.yml | 2 +- Dockerfile | 4 ++-- README.md | 2 +- go.mod | 3 +-- go.sum | 2 -- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b2c53776ad4..bbb76d3675c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,8 +5,8 @@ "build": { "dockerfile": "Dockerfile", "args": { - // Update the VARIANT arg to pick a version of Go: 1, 1.15, 1.14 - "VARIANT": "1.14", + // Update the VARIANT arg to pick a version of Go + "VARIANT": "1.16", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 30370178ca8..9cf371ca168 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -12,7 +12,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.14.2 + go-version: 1.16.4 - name: Checkout Merged Branch uses: actions/checkout@v2 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3efc51d287a..1eb137467ec 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,7 +10,7 @@ jobs: validate: strategy: matrix: - go-version: [1.14.x, 1.15.x] + go-version: [1.15.x, 1.16.x] os: [ubuntu-18.04] runs-on: ${{ matrix.os }} diff --git a/Dockerfile b/Dockerfile index d76398dc6d3..defb64c8586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget RUN cd /tmp && \ - wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \ - tar -xf go1.14.2.linux-amd64.tar.gz && \ + wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \ + tar -xf go1.16.4.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ diff --git a/README.md b/README.md index f3338ce1613..64486fd4393 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid- ## Installation -First install [Go](https://golang.org/doc/install) version 1.14 or newer. +First install [Go](https://golang.org/doc/install) version 1.15 or newer. Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. diff --git a/go.mod b/go.mod index 6d1fe334390..7d10a5c9bca 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prebid/prebid-server -go 1.14 +go 1.16 require ( github.com/BurntSushi/toml v0.3.1 // indirect @@ -22,7 +22,6 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 - github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/lib/pq v1.0.0 github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect diff --git a/go.sum b/go.sum index 78b21ae139c..4cb5863dd41 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= From 9489942cc0bc837e01384ca78caea307cfbcb81d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 22 Jun 2021 20:53:25 -0400 Subject: [PATCH 443/603] Friendlier Startup Error Messages (#1894) --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index ebeabf29df8..5c5a2a462e0 100644 --- a/main.go +++ b/main.go @@ -34,12 +34,12 @@ func main() { cfg, err := loadConfig() if err != nil { - glog.Fatalf("Configuration could not be loaded or did not pass validation: %v", err) + glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } err = serve(Rev, cfg) if err != nil { - glog.Errorf("prebid-server failed: %v", err) + glog.Exitf("prebid-server failed: %v", err) } } From e24356fdbde9d4a54edccbdd137209cb833bee43 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 23 Jun 2021 12:51:40 -0400 Subject: [PATCH 444/603] Second fix for weak vendor enforcement (#1896) --- gdpr/impl.go | 2 ++ gdpr/impl_test.go | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index 94c37e61d96..17a1a893e1c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -81,6 +81,8 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, if id, ok := p.vendorIDs[bidder]; ok { return p.allowActivities(ctx, id, consent, weakVendorEnforcement) + } else if weakVendorEnforcement { + return p.allowActivities(ctx, 0, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d1a6cad75e8..cde485467b3 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,11 +382,10 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, - openrtb_ext.BidderAudienceNetwork: 55, + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ From 74d84d52fd33deba117bdb4818a6e1401172380a Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 24 Jun 2021 19:51:35 +0300 Subject: [PATCH 445/603] Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 15 ++++++ adapters/rubicon/rubicon_test.go | 46 +++++++++++++++++++ .../rubicontest/exemplary/simple-video.json | 4 ++ 3 files changed, 65 insertions(+) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 89d69522fe8..73f6a5d39ca 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -750,6 +750,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } + resolvedBidFloor, resolvedBidFloorCur := resolveBidFloorAttributes(thisImp.BidFloor, thisImp.BidFloorCur) + thisImp.BidFloorCur = resolvedBidFloorCur + thisImp.BidFloor = resolvedBidFloor + if request.User != nil { userCopy := *request.User userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} @@ -893,6 +897,17 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } +// Will be replaced after https://github.com/prebid/prebid-server/issues/1482 resolution +func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, string) { + if bidFloor > 0 { + if strings.ToUpper(bidFloorCur) == "EUR" { + return bidFloor * 1.2, "USD" + } + } + + return bidFloor, bidFloorCur +} + func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { var segmentIdsToCopy = make([]string, 0) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index dc5b3a90423..8f8d3fb1557 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -572,6 +572,52 @@ func TestResolveVideoSizeId(t *testing.T) { } } +func TestResolveBidFloorAttributes(t *testing.T) { + testScenarios := []struct { + bidFloor float64 + bidFloorCur string + expectedBidFloor float64 + expectedBidFloorCur string + }{ + { + bidFloor: 1, + bidFloorCur: "EUR", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 1, + bidFloorCur: "Eur", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 0, + bidFloorCur: "EUR", + expectedBidFloor: 0, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: -1, + bidFloorCur: "EUR", + expectedBidFloor: -1, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: 1, + bidFloorCur: "USD", + expectedBidFloor: 1, + expectedBidFloorCur: "USD", + }, + } + + for _, scenario := range testScenarios { + bidFloor, bidFloorCur := resolveBidFloorAttributes(scenario.bidFloor, scenario.bidFloorCur) + assert.Equal(t, scenario.expectedBidFloor, bidFloor) + assert.Equal(t, scenario.expectedBidFloorCur, bidFloorCur) + } +} + func TestNoContentResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index b85c28def44..a90670e53be 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -71,6 +71,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1, + "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -192,6 +194,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1.2, + "bidfloorcur": "USD", "ext": { "rp": { "track": { From 952a1c9120bba7215f390987105779fb8bb13c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 24 Jun 2021 19:26:58 +0200 Subject: [PATCH 446/603] Outbrain adapter: overwrite tagid only if it exists (#1895) --- adapters/outbrain/outbrain.go | 6 +- .../supplemental/general_params.json | 139 ++++++++++++++++++ .../supplemental/optional_params.json | 3 + 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 adapters/outbrain/outbraintest/supplemental/general_params.json diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index 282a6d53aa0..6b121cb4732 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -43,8 +43,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errs = append(errs, err) continue } - imp.TagID = outbrainExt.TagId - reqCopy.Imp[i] = imp + if outbrainExt.TagId != "" { + imp.TagID = outbrainExt.TagId + reqCopy.Imp[i] = imp + } } publisher := &openrtb2.Publisher{ diff --git a/adapters/outbrain/outbraintest/supplemental/general_params.json b/adapters/outbrain/outbraintest/supplemental/general_params.json new file mode 100644 index 00000000000..b2a547c8b4e --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/general_params.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json index a69ceaa0c85..d75875e0e70 100644 --- a/adapters/outbrain/outbraintest/supplemental/optional_params.json +++ b/adapters/outbrain/outbraintest/supplemental/optional_params.json @@ -12,6 +12,7 @@ } ] }, + "tagid": "should-be-overwritten-tagid", "ext": { "bidder": { "publisher": { @@ -26,6 +27,8 @@ } } ], + "bcat": ["should-be-overwritten-bcat"], + "badv": ["should-be-overwritten-badv"], "site": { "page": "http://example.com" }, From beaf6432f6926b0f73c16da4d964a8d29fc5e4e0 Mon Sep 17 00:00:00 2001 From: bidmyadz <82382704+bidmyadz@users.noreply.github.com> Date: Wed, 30 Jun 2021 18:02:31 +0300 Subject: [PATCH 447/603] New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz --- adapters/bidmyadz/bidmyadz.go | 157 +++++++++++++++++ adapters/bidmyadz/bidmyadz_test.go | 18 ++ .../bidmyadztest/exemplary/banner.json | 146 ++++++++++++++++ .../bidmyadztest/exemplary/native.json | 141 +++++++++++++++ .../bidmyadztest/exemplary/video.json | 160 ++++++++++++++++++ .../bidmyadztest/params/race/banner.json | 3 + .../bidmyadztest/params/race/native.json | 3 + .../bidmyadztest/params/race/video.json | 3 + .../supplemental/invalid-device-fields.json | 48 ++++++ .../supplemental/invalid-multi-imps.json | 61 +++++++ .../supplemental/missing-mediatype.json | 122 +++++++++++++ .../supplemental/response-without-bids.json | 109 ++++++++++++ .../response-without-seatbid.json | 106 ++++++++++++ .../bidmyadztest/supplemental/status-204.json | 94 ++++++++++ .../bidmyadztest/supplemental/status-400.json | 101 +++++++++++ .../status-service-unavailable.json | 100 +++++++++++ .../supplemental/status-unknown.json | 101 +++++++++++ adapters/bidmyadz/params_test.go | 49 ++++++ adapters/bidmyadz/usersync.go | 12 ++ adapters/bidmyadz/usersync_test.go | 33 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/bidmyadz.yaml | 13 ++ static/bidder-params/bidmyadz.json | 12 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 27 files changed, 1601 insertions(+) create mode 100644 adapters/bidmyadz/bidmyadz.go create mode 100644 adapters/bidmyadz/bidmyadz_test.go create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-204.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-400.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json create mode 100644 adapters/bidmyadz/params_test.go create mode 100644 adapters/bidmyadz/usersync.go create mode 100644 adapters/bidmyadz/usersync_test.go create mode 100644 static/bidder-info/bidmyadz.yaml create mode 100644 static/bidder-params/bidmyadz.json diff --git a/adapters/bidmyadz/bidmyadz.go b/adapters/bidmyadz/bidmyadz.go new file mode 100644 index 00000000000..829d57e606f --- /dev/null +++ b/adapters/bidmyadz/bidmyadz.go @@ -0,0 +1,157 @@ +package bidmyadz + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + var errors []error + + if len(openRTBRequest.Imp) > 1 { + errors = append(errors, &errortypes.BadInput{ + Message: "Bidder does not support multi impression", + }) + } + + if openRTBRequest.Device.IP == "" && openRTBRequest.Device.IPv6 == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "IP/IPv6 is a required field", + }) + } + + if openRTBRequest.Device.UA == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "User-Agent is a required field", + }) + } + + if len(errors) != 0 { + return nil, errors + } + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.endpoint, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bidder is unavailable. Please contact your account manager.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong. Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid.Bids", + }} + } + + bid := bids[0] + + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("BidExt parsing error. %s", err.Error()), + }} + } + + bidType, err := getBidType(bidExt) + + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + return bidResponse, nil +} + +func getBidType(ext bidExt) (openrtb_ext.BidType, error) { + return openrtb_ext.ParseBidType(ext.MediaType) +} diff --git a/adapters/bidmyadz/bidmyadz_test.go b/adapters/bidmyadz/bidmyadz_test.go new file mode 100644 index 00000000000..b0de6a02956 --- /dev/null +++ b/adapters/bidmyadz/bidmyadz_test.go @@ -0,0 +1,18 @@ +package bidmyadz + +import ( + "github.com/stretchr/testify/assert" + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderBidmyadz, config.Adapter{ + Endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "bidmyadztest", bidder) +} diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/banner.json b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json new file mode 100644 index 00000000000..460291bc4f0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/native.json b/adapters/bidmyadz/bidmyadztest/exemplary/native.json new file mode 100644 index 00000000000..984802601c0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/native.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/video.json b/adapters/bidmyadz/bidmyadztest/exemplary/video.json new file mode 100644 index 00000000000..92c42f8331a --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/video.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/banner.json b/adapters/bidmyadz/bidmyadztest/params/race/banner.json new file mode 100644 index 00000000000..18dce42f2c4 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "placementId": "tbanner" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/native.json b/adapters/bidmyadz/bidmyadztest/params/race/native.json new file mode 100644 index 00000000000..0600af3a894 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "placementId": "tnative" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/video.json b/adapters/bidmyadz/bidmyadztest/params/race/video.json new file mode 100644 index 00000000000..85478bf22c5 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "placementId": "tvideo" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json new file mode 100644 index 00000000000..d620a050632 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "IP/IPv6 is a required field", + "comparison": "literal" + }, { + "value": "User-Agent is a required field", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json new file mode 100644 index 00000000000..09020fc89e9 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }, { + "id": "1", + "secure": 1, + "bidfloor": 0.11, + "bidfloorcur": "USD", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "placementId": "3234" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "Bidder does not support multi impression", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json new file mode 100644 index 00000000000..486d7324dfc --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "BidExt parsing error. unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json new file mode 100644 index 00000000000..fe3361f69d8 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid.Bids", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json new file mode 100644 index 00000000000..727e745e762 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json new file mode 100644 index 00000000000..05efda0e9f3 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json new file mode 100644 index 00000000000..e24acdc6766 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "Source blocked" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"Source blocked\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json new file mode 100644 index 00000000000..13e22f55889 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bidder is unavailable. Please contact your account manager.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json new file mode 100644 index 00000000000..69b649e19ad --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Forbidden" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong. Status Code: [ 403 ] \"Forbidden\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/params_test.go b/adapters/bidmyadz/params_test.go new file mode 100644 index 00000000000..857cde86d22 --- /dev/null +++ b/adapters/bidmyadz/params_test.go @@ -0,0 +1,49 @@ +package bidmyadz + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "placementId": "1234" }`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderBidmyadz, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected bidmyadz params: %s", validParam) + } + } +} + +var invalidParams = []string{ + `1234`, + ``, + `true`, + `null`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "placementId": null }`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderBidmyadz, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/bidmyadz/usersync.go b/adapters/bidmyadz/usersync.go new file mode 100644 index 00000000000..755a184d6e4 --- /dev/null +++ b/adapters/bidmyadz/usersync.go @@ -0,0 +1,12 @@ +package bidmyadz + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewBidmyadzSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("bidmyadz", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/bidmyadz/usersync_test.go b/adapters/bidmyadz/usersync_test.go new file mode 100644 index 00000000000..11b5fedd73f --- /dev/null +++ b/adapters/bidmyadz/usersync_test.go @@ -0,0 +1,33 @@ +package bidmyadz + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewBidmyadzSyncer(t *testing.T) { + syncURL := "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewBidmyadzSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 58bb40a592c..47db6b57b9c 100644 --- a/config/config.go +++ b/config/config.go @@ -587,6 +587,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBidmyadz, "https://cookie-sync.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&red="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbidmyadz%26uid%3D%5BUID%5D%26us_privacy%3D{{.USPrivacy}}%26gdpr_consent%3D{{.GDPRConsent}}%26gdpr%3D{{.GDPR}}") // openrtb_ext.BidderBidsCube doesn't have a good default. // openrtb_ext.BidderBmtm doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -842,6 +843,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}") v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io") + v.SetDefault("adapters.bidmyadz.endpoint", "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc") v.SetDefault("adapters.bidscube.endpoint", "http://supply.bidscube.com/?c=o&m=rtb") v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1fdb7c2489d..dd6638126c9 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -33,6 +33,7 @@ import ( "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" "github.com/prebid/prebid-server/adapters/bidmachine" + "github.com/prebid/prebid-server/adapters/bidmyadz" "github.com/prebid/prebid-server/adapters/bidscube" "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" @@ -154,6 +155,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderBeintoo: beintoo.Builder, openrtb_ext.BidderBetween: between.Builder, openrtb_ext.BidderBidmachine: bidmachine.Builder, + openrtb_ext.BidderBidmyadz: bidmyadz.Builder, openrtb_ext.BidderBidsCube: bidscube.Builder, openrtb_ext.BidderBmtm: bmtm.Builder, openrtb_ext.BidderBrightroll: brightroll.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 91ce9f0d27f..d6075bb15b5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -104,6 +104,7 @@ const ( BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" BidderBidmachine BidderName = "bidmachine" + BidderBidmyadz BidderName = "bidmyadz" BidderBidsCube BidderName = "bidscube" BidderBmtm BidderName = "bmtm" BidderBrightroll BidderName = "brightroll" @@ -225,6 +226,7 @@ func CoreBidderNames() []BidderName { BidderBeintoo, BidderBetween, BidderBidmachine, + BidderBidmyadz, BidderBidsCube, BidderBmtm, BidderBrightroll, diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml new file mode 100644 index 00000000000..70a995a2798 --- /dev/null +++ b/static/bidder-info/bidmyadz.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "contact@bidmyadz.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/bidmyadz.json b/static/bidder-params/bidmyadz.json new file mode 100644 index 00000000000..4e7b1119e08 --- /dev/null +++ b/static/bidder-params/bidmyadz.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BidMyAdz Adapter Params", + "description": "A schema which validates params accepted by the BidMyAdz adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string" + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 169417e07e8..7de59491e02 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -28,6 +28,7 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/bidmyadz" "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" @@ -128,6 +129,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBmtm, bmtm.NewBmtmSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderBidmyadz, bidmyadz.NewBidmyadzSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 0cea346e423..d86efea30d8 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -37,6 +37,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBeachfront): syncConfig, string(openrtb_ext.BidderBeintoo): syncConfig, string(openrtb_ext.BidderBetween): syncConfig, + string(openrtb_ext.BidderBidmyadz): syncConfig, string(openrtb_ext.BidderBmtm): syncConfig, string(openrtb_ext.BidderBrightroll): syncConfig, string(openrtb_ext.BidderColossus): syncConfig, From c0a638cfaff9aca69c5a410eaebfe131df4485a1 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 30 Jun 2021 12:54:40 -0400 Subject: [PATCH 448/603] Currency Conversion Utility Function (#1901) --- adapters/bidder.go | 20 ++++++ adapters/bidder_test.go | 63 +++++++++++++++++ currency/aggregate_conversions.go | 2 +- currency/aggregate_conversions_test.go | 2 +- currency/constant_rates.go | 2 +- currency/errors.go | 6 +- currency/rates.go | 6 +- exchange/bidder_test.go | 20 +++--- exchange/exchange.go | 2 +- exchange/exchange_test.go | 94 ++++++++++++++++++++++++-- 10 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 adapters/bidder_test.go diff --git a/adapters/bidder.go b/adapters/bidder.go index 2e5ec83c849..b7bde4bc55d 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -7,6 +7,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -138,6 +139,25 @@ func (r *RequestData) SetBasicAuth(username string, password string) { type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string + currencyConversions currency.Conversions +} + +func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { + return ExtraRequestInfo{ + currencyConversions: c, + } +} + +// ConvertCurrency converts a given amount from one currency to another, or returns: +// - Error if the `from` or `to` arguments are malformed or unknown ISO-4217 codes. +// - ConversionNotFoundError if the conversion mapping is unknown to Prebid Server +// and not provided in the bid request. +func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float64, error) { + if rate, err := r.currencyConversions.GetRate(from, to); err == nil { + return value * rate, nil + } else { + return 0, err + } } type Builder func(openrtb_ext.BidderName, config.Adapter) (Bidder, error) diff --git a/adapters/bidder_test.go b/adapters/bidder_test.go new file mode 100644 index 00000000000..f0b833cec2e --- /dev/null +++ b/adapters/bidder_test.go @@ -0,0 +1,63 @@ +package adapters + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestExtraRequestInfoConvertCurrency(t *testing.T) { + var ( + givenValue float64 = 2 + givenFrom string = "AAA" + givenTo string = "BBB" + ) + + testCases := []struct { + description string + setMock func(m *mock.Mock) + expectedValue float64 + expectedError error + }{ + { + description: "Success", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, nil) }, + expectedValue: 5, + expectedError: nil, + }, + { + description: "Error", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, errors.New("some error")) }, + expectedValue: 0, + expectedError: errors.New("some error"), + }, + } + + for _, test := range testCases { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + + extraRequestInfo := NewExtraRequestInfo(mockConversions) + result, err := extraRequestInfo.ConvertCurrency(givenValue, givenFrom, givenTo) + + mockConversions.AssertExpectations(t) + assert.Equal(t, test.expectedValue, result, test.description+":result") + assert.Equal(t, test.expectedError, err, test.description+":err") + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go index 53c5ebff4b6..a15404fe501 100644 --- a/currency/aggregate_conversions.go +++ b/currency/aggregate_conversions.go @@ -23,7 +23,7 @@ func (re *AggregateConversions) GetRate(from string, to string) (float64, error) rate, err := re.customRates.GetRate(from, to) if err == nil { return rate, nil - } else if _, isMissingRateErr := err.(ConversionRateNotFound); !isMissingRateErr { + } else if _, isMissingRateErr := err.(ConversionNotFoundError); !isMissingRateErr { // other error, return the error return 0, err } diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go index 35ca51a1fe7..773a596c28c 100644 --- a/currency/aggregate_conversions_test.go +++ b/currency/aggregate_conversions_test.go @@ -65,7 +65,7 @@ func TestGroupedGetRate(t *testing.T) { }, }, { - expectedError: ConversionRateNotFound{"GBP", "EUR"}, + expectedError: ConversionNotFoundError{FromCur: "GBP", ToCur: "EUR"}, testCases: []aTest{ {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, }, diff --git a/currency/constant_rates.go b/currency/constant_rates.go index dde317d809e..ccc5c24d3fc 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -27,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go index d764c15b984..bb4c42aa90a 100644 --- a/currency/errors.go +++ b/currency/errors.go @@ -2,12 +2,12 @@ package currency import "fmt" -// ConversionRateNotFound is thrown by the currency.Conversions GetRate(from string, to string) method +// ConversionNotFoundError is thrown by the currency.Conversions GetRate(from string, to string) method // when the conversion rate between the two currencies, nor its reciprocal, can be found. -type ConversionRateNotFound struct { +type ConversionNotFoundError struct { FromCur, ToCur string } -func (err ConversionRateNotFound) Error() string { +func (err ConversionNotFoundError) Error() string { return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur) } diff --git a/currency/rates.go b/currency/rates.go index 62914c4b2e2..b9cb0201b38 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -47,9 +47,9 @@ func (r *Rates) UnmarshalJSON(b []byte) error { // GetRate returns the conversion rate between two currencies or: // - An error if one of the currency strings is not well-formed // - An error if any of the currency strings is not a recognized currency code. -// - A MissingConversionRate error in case the conversion rate between the two +// - A ConversionNotFoundError in case the conversion rate between the two // given currencies is not in the currencies rates map -func (r *Rates) GetRate(from string, to string) (float64, error) { +func (r *Rates) GetRate(from, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) if err != nil { @@ -70,7 +70,7 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { // In case we have an entry TO -> FROM return 1 / conversion, nil } - return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index deff066200a..213af9a0f05 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"JPY", "USD"}, + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -587,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"JPY", "USD"}, - currency.ConversionRateNotFound{"BZD", "USD"}, - currency.ConversionRateNotFound{"DKK", "USD"}, + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "BZD", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "DKK", ToCur: "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -720,9 +720,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -754,7 +754,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -762,7 +762,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"GBP", "USD"}, + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -770,7 +770,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"GBP", "USD"}, + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index e96aadbce86..6aaafcd1ad6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -449,7 +449,7 @@ func (e *exchange) getAllBids( if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok { adjustmentFactor = givenAdjustment } - var reqInfo adapters.ExtraRequestInfo + reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2d1306af093..2a61a4e8454 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -31,6 +31,7 @@ import ( "github.com/buger/jsonparser" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" ) @@ -539,7 +540,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { func TestOverrideWithCustomCurrency(t *testing.T) { - mockCurrencyClient := &mockCurrencyRatesClient{ + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } mockCurrencyConverter := currency.NewRateConverter( @@ -694,6 +695,72 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } } +func TestAdapterCurrency(t *testing.T) { + fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + currencyConverter := currency.NewRateConverter( + fakeCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + currencyConverter.Run() + + // Initialize Mock Bidder + // - Response purposefully causes PBS-Core to stop processing the request, since this test is only + // interested in the call to MakeRequests and nothing after. + mockBidder := &mockBidder{} + mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil)) + + // Initialize Real Exchange + e := exchange{ + cache: &wellBehavedCache{}, + me: &metricsConf.DummyMetricsEngine{}, + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currencyConverter, + categoriesFetcher: nilCategoryFetcher{}, + bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderName("foo"), nil), + }, + } + + // Define Bid Request + request := &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(`{"foo": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":0}`), + }, + Cur: []string{"USD"}, + Ext: json.RawMessage(`{"prebid": {"currency": {"rates": {"USD": {"MXN": 20.00}}}}}`), + } + + // Run Auction + auctionRequest := AuctionRequest{ + BidRequest: request, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + assert.NoError(t, err) + assert.Equal(t, "some-request-id", response.ID, "Response ID") + assert.Empty(t, response.SeatBid, "Response Bids") + assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") + + // Test Currency Converter Properly Passed To Adapter + if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { + converted, err := mockBidder.lastExtraRequestInfo.ConvertCurrency(2.0, "USD", "MXN") + assert.NoError(t, err, "Currency Conversion Error") + assert.Equal(t, 40.0, converted, "Currency Conversion Response") + } +} + func TestGetAuctionCurrencyRates(t *testing.T) { pbsRates := map[string]map[string]float64{ @@ -859,7 +926,7 @@ func TestGetAuctionCurrencyRates(t *testing.T) { } // Init mock currency conversion service - mockCurrencyClient := &mockCurrencyRatesClient{ + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, } mockCurrencyConverter := currency.NewRateConverter( @@ -3654,15 +3721,32 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } -// mockCurrencyRatesClient is a simple http client mock returning a constant response body -type mockCurrencyRatesClient struct { +// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type fakeCurrencyRatesHttpClient struct { responseBody string } -func (m *mockCurrencyRatesClient) Do(req *http.Request) (*http.Response, error) { +func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { return &http.Response{ Status: "200 OK", StatusCode: http.StatusOK, Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), }, nil } + +type mockBidder struct { + mock.Mock + lastExtraRequestInfo *adapters.ExtraRequestInfo +} + +func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + m.lastExtraRequestInfo = reqInfo + + args := m.Called(request, reqInfo) + return args.Get(0).([]*adapters.RequestData), args.Get(1).([]error) +} + +func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + args := m.Called(internalRequest, externalRequest, response) + return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error) +} From 8335d83bef45b6e249a845c2d68ec1062f78f527 Mon Sep 17 00:00:00 2001 From: lunamedia <73552749+lunamedia@users.noreply.github.com> Date: Wed, 30 Jun 2021 23:44:01 +0300 Subject: [PATCH 449/603] New Adapter: SA Lunamedia (#1891) --- adapters/sa_lunamedia/params_test.go | 52 ++++++ adapters/sa_lunamedia/salunamedia.go | 132 ++++++++++++++ adapters/sa_lunamedia/salunamedia_test.go | 18 ++ .../salunamediatest/exemplary/banner.json | 142 +++++++++++++++ .../salunamediatest/exemplary/native.json | 132 ++++++++++++++ .../salunamediatest/exemplary/video.json | 169 ++++++++++++++++++ .../salunamediatest/params/race/banner.json | 3 + .../salunamediatest/params/race/native.json | 3 + .../salunamediatest/params/race/video.json | 3 + .../supplemental/bad-response.json | 98 ++++++++++ .../supplemental/empty-seatbid.json | 102 +++++++++++ .../supplemental/status-204.json | 92 ++++++++++ .../supplemental/status-400.json | 99 ++++++++++ .../supplemental/status-503.json | 98 ++++++++++ .../supplemental/unexpected-status.json | 99 ++++++++++ adapters/sa_lunamedia/usersync.go | 12 ++ adapters/sa_lunamedia/usersync_test.go | 33 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_sa_lunamedia.go | 6 + static/bidder-info/sa_lunamedia.yaml | 14 ++ static/bidder-params/sa_lunamedia.json | 17 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1333 insertions(+) create mode 100644 adapters/sa_lunamedia/params_test.go create mode 100644 adapters/sa_lunamedia/salunamedia.go create mode 100644 adapters/sa_lunamedia/salunamedia_test.go create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json create mode 100644 adapters/sa_lunamedia/usersync.go create mode 100644 adapters/sa_lunamedia/usersync_test.go create mode 100644 openrtb_ext/imp_sa_lunamedia.go create mode 100644 static/bidder-info/sa_lunamedia.yaml create mode 100644 static/bidder-params/sa_lunamedia.json diff --git a/adapters/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go new file mode 100644 index 00000000000..bf7a1f493e6 --- /dev/null +++ b/adapters/sa_lunamedia/params_test.go @@ -0,0 +1,52 @@ +package salunamedia + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "2", "type": "network"}`, + `{ "key": "1"}`, + `{ "key": "33232", "type": "publisher"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSaLunaMedia, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected sa_lunamedia params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "type": "network" }`, + `{ "key": "asddsfd", "type": "any"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSaLunaMedia, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go new file mode 100644 index 00000000000..ea6e12b01d6 --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia.go @@ -0,0 +1,132 @@ +package salunamedia + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.endpoint, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder unavailable. Please contact the bidder support.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid.Bids", + }} + } + + bid := bids[0] + + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Missing BidExt", + }} + } + + bidType, err := getBidType(bidExt) + + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + return bidResponse, nil +} + +func getBidType(ext bidExt) (openrtb_ext.BidType, error) { + return openrtb_ext.ParseBidType(ext.MediaType) +} diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go new file mode 100644 index 00000000000..f5d2058208e --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia_test.go @@ -0,0 +1,18 @@ +package salunamedia + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSaLunaMedia, config.Adapter{ + Endpoint: "http://test.com/pserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "salunamediatest", bidder) +} diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json new file mode 100644 index 00000000000..2ce4ad81106 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/native.json b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json new file mode 100644 index 00000000000..74d8940f0a1 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "native" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/video.json b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json new file mode 100644 index 00000000000..9a042d726d9 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "key": "test" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "video" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/native.json b/adapters/sa_lunamedia/salunamediatest/params/race/native.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/video.json b/adapters/sa_lunamedia/salunamediatest/params/race/video.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json new file mode 100644 index 00000000000..6373207d481 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..8942b3be65a --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json new file mode 100644 index 00000000000..042b96bde65 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json new file mode 100644 index 00000000000..1ecdc46e5fa --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json new file mode 100644 index 00000000000..2590418a75f --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bidder unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..a54737cafdb --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Access is denied" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Status Code: [ 403 ] \"Access is denied\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/usersync.go b/adapters/sa_lunamedia/usersync.go new file mode 100644 index 00000000000..f78b7944cb2 --- /dev/null +++ b/adapters/sa_lunamedia/usersync.go @@ -0,0 +1,12 @@ +package salunamedia + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSaLunamediaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("salunamedia", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/sa_lunamedia/usersync_test.go b/adapters/sa_lunamedia/usersync_test.go new file mode 100644 index 00000000000..e3820fbc1af --- /dev/null +++ b/adapters/sa_lunamedia/usersync_test.go @@ -0,0 +1,33 @@ +package salunamedia + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewSaLunamediaSyncer(t *testing.T) { + syncURL := "https://test.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewSaLunamediaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://test.com/pserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 47db6b57b9c..f6fa9917178 100644 --- a/config/config.go +++ b/config/config.go @@ -618,6 +618,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSaLunaMedia, "https://cookie.lmgssp.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsa_lunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderMadvertise doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderMediafuse doesn't have a good default. @@ -880,6 +881,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver") v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index dd6638126c9..17e0d4f54eb 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -89,6 +89,7 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/silvermob" "github.com/prebid/prebid-server/adapters/smaato" @@ -191,6 +192,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderLockerDome: lockerdome.Builder, openrtb_ext.BidderLogicad: logicad.Builder, openrtb_ext.BidderLunaMedia: lunamedia.Builder, + openrtb_ext.BidderSaLunaMedia: salunamedia.Builder, openrtb_ext.BidderMadvertise: madvertise.Builder, openrtb_ext.BidderMarsmedia: marsmedia.Builder, openrtb_ext.BidderMediafuse: adtelligent.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index d6075bb15b5..f2dd9815e8e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -140,6 +140,7 @@ const ( BidderLockerDome BidderName = "lockerdome" BidderLogicad BidderName = "logicad" BidderLunaMedia BidderName = "lunamedia" + BidderSaLunaMedia BidderName = "sa_lunamedia" BidderMadvertise BidderName = "madvertise" BidderMarsmedia BidderName = "marsmedia" BidderMediafuse BidderName = "mediafuse" @@ -262,6 +263,7 @@ func CoreBidderNames() []BidderName { BidderLockerDome, BidderLogicad, BidderLunaMedia, + BidderSaLunaMedia, BidderMadvertise, BidderMarsmedia, BidderMediafuse, diff --git a/openrtb_ext/imp_sa_lunamedia.go b/openrtb_ext/imp_sa_lunamedia.go new file mode 100644 index 00000000000..cb99b0ac561 --- /dev/null +++ b/openrtb_ext/imp_sa_lunamedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpSaLunamedia struct { + Key string `json:"key"` + Type string `json:"type,omitempty"` +} diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml new file mode 100644 index 00000000000..181e1fd6c73 --- /dev/null +++ b/static/bidder-info/sa_lunamedia.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@lunamedia.io" +gvlVendorID: 998 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/sa_lunamedia.json b/static/bidder-params/sa_lunamedia.json new file mode 100644 index 00000000000..51ca09098e2 --- /dev/null +++ b/static/bidder-params/sa_lunamedia.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sa_Lunamedia Adapter Params", + "description": "A schema which validates params accepted by the Sa_Lunamedia adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + }, + "type": { + "type": "string", + "enum": ["network", "publisher"] + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7de59491e02..7472bf3a70d 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -70,6 +70,7 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" @@ -156,6 +157,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSaLunaMedia, salunamedia.NewSaLunamediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMediafuse, mediafuse.NewMediafuseSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index d86efea30d8..81216b19199 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -65,6 +65,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, + string(openrtb_ext.BidderSaLunaMedia): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMediafuse): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, From b996cf9c01565e86e5517650c623a9dc6943d260 Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 1 Jul 2021 08:49:34 -0700 Subject: [PATCH 450/603] Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy --- .../adformtest/supplemental/user-nil.json | 7 +- adapters/dmx/dmx.go | 2 +- adapters/dmx/dmx_test.go | 77 +++++-------------- adapters/rubicon/rubicon.go | 14 ++-- adapters/rubicon/rubicon_test.go | 10 +-- .../rubicontest/exemplary/simple-video.json | 1 - endpoints/openrtb2/amp_auction_test.go | 12 +-- endpoints/openrtb2/auction.go | 6 +- .../invalid-whole/digitrust.json | 46 ----------- .../valid-whole/supplementary/digitrust.json | 50 ------------ exchange/exchange_test.go | 4 +- .../exchangetest/request-other-user-ext.json | 14 +--- .../exchangetest/request-user-no-prebid.json | 10 --- exchange/utils.go | 2 +- exchange/utils_test.go | 4 +- openrtb_ext/user.go | 13 ---- privacy/scrubber.go | 5 +- privacy/scrubber_test.go | 35 +++------ 18 files changed, 46 insertions(+), 266 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json delete mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json index 5f02fe85971..96ea1dbff71 100644 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ b/adapters/adform/adformtest/supplemental/user-nil.json @@ -31,12 +31,7 @@ }, "user": { "ext": { - "consent": "abc2", - "digitrust": { - "ID": "digitrustId", - "KeyV": 1, - "Pref": 0 - } + "consent": "abc2" } } }, diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 7124d229347..adcec4a33c5 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -148,7 +148,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt } if dmxReq.User.Ext != nil { if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil { - if len(userExt.Eids) > 0 || (userExt.DigiTrust != nil && userExt.DigiTrust.ID != "") { + if len(userExt.Eids) > 0 { anyHasId = true } } diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index a9f1e8bbc79..409290c110d 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -643,50 +643,6 @@ func TestUserEidsOnly(t *testing.T) { } } -func TestUserDigitrustOnly(t *testing.T) { - var w, h int = 300, 250 - - var width, height int64 = int64(w), int64(h) - - bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - imp1 := openrtb2.Imp{ - ID: "imp1", - Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb2.Banner{ - W: &width, - H: &height, - Format: []openrtb2.Format{ - {W: 300, H: 250}, - }, - }} - - inputRequest := openrtb2.BidRequest{ - Imp: []openrtb2.Imp{imp1, imp1, imp1}, - Site: &openrtb2.Site{ - Publisher: &openrtb2.Publisher{ - ID: "10007", - }, - }, - User: &openrtb2.User{Ext: json.RawMessage(`{ - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, - ID: "1234", - } - - actualAdapterRequests, _ := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) - if len(actualAdapterRequests) != 1 { - t.Errorf("should have 1 request") - } -} - func TestUsersEids(t *testing.T) { var w, h int = 300, 250 @@ -725,53 +681,56 @@ func TestUsersEids(t *testing.T) { "rtiPartner": "TDID" } }] - },{ + }, + { "source": "pubcid.org", "uids": [{ - "id":"11111111" + "id": "11111111" }] }, - { + { "source": "id5-sync.com", "uids": [{ "id": "ID5-12345" }] - }, - { + }, + { "source": "parrable.com", "uids": [{ "id": "01.1563917337.test-eid" }] - },{ + }, + { "source": "identityLink", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "criteo", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "britepool.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "liveintent.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "netid.de", "uids": [{ "id": "11111111" }] - }], - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, + }] + }`)}, ID: "1234", } diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 73f6a5d39ca..84d8596449c 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -96,12 +96,11 @@ type rubiconUserDataExt struct { } type rubiconUserExt struct { - Consent string `json:"consent,omitempty"` - DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` - Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` - TpID []rubiconExtUserTpID `json:"tpid,omitempty"` - RP rubiconUserExtRP `json:"rp"` - LiverampIdl string `json:"liveramp_idl,omitempty"` + Consent string `json:"consent,omitempty"` + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + TpID []rubiconExtUserTpID `json:"tpid,omitempty"` + RP rubiconUserExtRP `json:"rp"` + LiverampIdl string `json:"liveramp_idl,omitempty"` } type rubiconSiteExtRP struct { @@ -772,9 +771,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } userExtRP.Consent = userExt.Consent - if userExt.DigiTrust != nil { - userExtRP.DigiTrust = userExt.DigiTrust - } userExtRP.Eids = userExt.Eids // set user.ext.tpid diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 8f8d3fb1557..3674c872d73 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1039,11 +1039,7 @@ func TestOpenRTBRequest(t *testing.T) { PxRatio: rubidata.devicePxRatio, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"digitrust": { - "id": "some-digitrust-id", - "keyv": 1, - "pref": 0 - }, + Ext: json.RawMessage(`{ "eids": [{ "source": "pubcid", "id": "2402fc76-7b39-4f0e-bfc2-060ef7693648" @@ -1117,10 +1113,6 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request.user.ext object.") } - assert.Equal(t, "some-digitrust-id", userExt.DigiTrust.ID, "DigiTrust ID id not as expected!") - assert.Equal(t, 1, userExt.DigiTrust.KeyV, "DigiTrust KeyV id not as expected!") - assert.Equal(t, 0, userExt.DigiTrust.Pref, "DigiTrust Pref id not as expected!") - assert.NotNil(t, userExt.Eids) assert.Equal(t, 1, len(userExt.Eids), "Eids values are not as expected!") assert.Contains(t, userExt.Eids, openrtb_ext.ExtUserEid{Source: "pubcid", ID: "2402fc76-7b39-4f0e-bfc2-060ef7693648"}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index a90670e53be..408cbb7979d 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -146,7 +146,6 @@ } ], "ext": { - "digitrust": null, "rp": { "target": { "iab": [ diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 079b9adb6d4..61164bd7272 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -32,7 +32,6 @@ func TestGoodAmpRequests(t *testing.T) { goodRequests := map[string]json.RawMessage{ "1": json.RawMessage(validRequest(t, "aliased-buyeruids.json")), "2": json.RawMessage(validRequest(t, "aliases.json")), - "4": json.RawMessage(validRequest(t, "digitrust.json")), "5": json.RawMessage(validRequest(t, "gdpr-no-consentstring.json")), "6": json.RawMessage(validRequest(t, "gdpr.json")), "7": json.RawMessage(validRequest(t, "site.json")), @@ -122,11 +121,6 @@ func TestAMPPageInfo(t *testing.T) { func TestGDPRConsent(t *testing.T) { consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" - digitrust := &openrtb_ext.ExtUserDigiTrust{ - ID: "anyDigitrustID", - KeyV: 1, - Pref: 0, - } testCases := []struct { description string @@ -165,12 +159,10 @@ func TestGDPRConsent(t *testing.T) { description: "Overrides Existing Consent - With Sibling Data", consent: consent, userExt: &openrtb_ext.ExtUser{ - Consent: existingConsent, - DigiTrust: digitrust, + Consent: existingConsent, }, expectedUserExt: openrtb_ext.ExtUser{ - Consent: consent, - DigiTrust: digitrust, + Consent: consent, }, }, { diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d8a7fa689b9..c9f2bbdb68f 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1124,13 +1124,9 @@ func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]s } if user.Ext != nil { - // Creating ExtUser object to check if DigiTrust is valid + // Creating ExtUser object var userExt openrtb_ext.ExtUser if err := json.Unmarshal(user.Ext, &userExt); err == nil { - if userExt.DigiTrust != nil && userExt.DigiTrust.Pref != 0 { - // DigiTrust is not valid. Return error. - return errors.New("request.user contains a digitrust object that is not valid.") - } // Check if the buyeruids are valid if userExt.Prebid != nil { if len(userExt.Prebid.BuyerUIDs) < 1 { diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json b/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json deleted file mode 100644 index 1fb7169fced..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "description": "Invalid digitrust object in user extension", - "mockBidRequest": { - "id": "request-with-invalid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 1 - } - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user contains a digitrust object that is not valid.\n" -} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json deleted file mode 100644 index 5cd070745ab..00000000000 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "Well formed amp request with digitrust extension that should run properly", - "mockBidRequest": { - "id": "request-with-valid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 0 - } - } - } - }, - "expectedBidResponse": { - "id":"request-with-valid-digitrust-obj", - "bidid":"test bid id", - "nbr":0 - }, - "expectedReturnCode": 200 -} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2a61a4e8454..989e67078eb 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1787,7 +1787,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ COPPA: 1, @@ -1949,7 +1949,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json index 9bd4c02fb42..f9fb3264c3c 100644 --- a/exchange/exchangetest/request-other-user-ext.json +++ b/exchange/exchangetest/request-other-user-ext.json @@ -12,11 +12,6 @@ "buyeruids": { "appnexus": "explicit-appnexus" } - }, - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 } } }, @@ -48,14 +43,7 @@ }, "user": { "id": "foo", - "buyeruid": "explicit-appnexus", - "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } - } + "buyeruid": "explicit-appnexus" }, "imp": [ { diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json index bb36ba8aeeb..aae11606baa 100644 --- a/exchange/exchangetest/request-user-no-prebid.json +++ b/exchange/exchangetest/request-user-no-prebid.json @@ -8,11 +8,6 @@ "user": { "id": "foo", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ @@ -45,11 +40,6 @@ "id": "foo", "buyeruid": "implicit-appnexus", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ diff --git a/exchange/utils.go b/exchange/utils.go index 3def0425819..0befeacdedd 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -332,7 +332,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { userExt.Prebid = nil // Remarshal (instead of removing) if the ext has other known fields - if userExt.Consent != "" || userExt.DigiTrust != nil || len(userExt.Eids) > 0 { + if userExt.Consent != "" || len(userExt.Eids) > 0 { if newUserExtBytes, err := json.Marshal(userExt); err != nil { return nil, err } else { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5a281f9a360..5a9fa187f62 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1788,7 +1788,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), @@ -1833,7 +1833,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index b83f82330db..d5e6ae678cc 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -10,11 +10,6 @@ type ExtUser struct { Prebid *ExtUserPrebid `json:"prebid,omitempty"` - // DigiTrust breaks the typical Prebid Server convention of namespacing "global" options inside "ext.prebid.*" - // to match the recommendation from the broader digitrust community. - // For more info, see: https://github.com/digi-trust/dt-cdn/wiki/OpenRTB-extension#openrtb-2x - DigiTrust *ExtUserDigiTrust `json:"digitrust,omitempty"` - Eids []ExtUserEid `json:"eids,omitempty"` } @@ -23,14 +18,6 @@ type ExtUserPrebid struct { BuyerUIDs map[string]string `json:"buyeruids,omitempty"` } -// ExtUserDigiTrust defines the contract for bidrequest.user.ext.digitrust -// More info on DigiTrust can be found here: https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide -type ExtUserDigiTrust struct { - ID string `json:"id"` // Unique device identifier - KeyV int `json:"keyv"` // Key version used to encrypt ID - Pref int `json:"pref"` // User optout preference -} - // ExtUserEid defines the contract for bidrequest.user.ext.eids // Responsible for the Universal User ID support: establishing pseudonymous IDs for users. // See https://github.com/prebid/Prebid.js/issues/3900 for details. diff --git a/privacy/scrubber.go b/privacy/scrubber.go index edaa5bb07c6..e07ebd0581b 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -225,11 +225,8 @@ func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage { } _, hasEids := userExtParsed["eids"] - _, hasDigitrust := userExtParsed["digitrust"] - if hasEids || hasDigitrust { + if hasEids { delete(userExtParsed, "eids") - delete(userExtParsed, "digitrust") - result, err := json.Marshal(userExtParsed) if err == nil { return result diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 9207315f593..1198d0bbc9c 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -202,7 +202,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -327,7 +327,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserNone, @@ -340,7 +340,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, @@ -359,7 +359,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -586,18 +586,18 @@ func TestScrubUserExtIDs(t *testing.T) { expected: json.RawMessage(`{"anyExisting":42}}`), }, { - description: "Remove eids + digitrust", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids", + userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{}`), }, { - description: "Remove eids + digitrust - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - With Other Data", + userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":42}`), }, { - description: "Remove eids + digitrust - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - With Other Nested Data", + userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, { @@ -620,21 +620,6 @@ func TestScrubUserExtIDs(t *testing.T) { userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, - { - description: "Remove digitrust Only", - userExt: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{}`), - }, - { - description: "Remove digitrust Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove digitrust Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { From 394bbadd0ffb7342e4b46fafedbc3a78f6f2443c Mon Sep 17 00:00:00 2001 From: Mani Gandham Date: Thu, 1 Jul 2021 10:21:22 -0700 Subject: [PATCH 451/603] IX: merge eventtrackers with imptrackers for native bid responses (#1900) --- adapters/ix/ix.go | 57 +++++++++- .../native-eventtrackers-compat-12.json | 104 ++++++++++++++++++ .../ix/ixtest/supplemental/bad-imp-id.json | 2 +- .../native-eventtrackers-empty.json | 104 ++++++++++++++++++ .../native-eventtrackers-missing.json | 104 ++++++++++++++++++ .../ixtest/supplemental/native-missing.json | 104 ++++++++++++++++++ 6 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json create mode 100644 adapters/ix/ixtest/supplemental/native-missing.json diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 5e10138f8f3..b251ec0f736 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -7,7 +7,10 @@ import ( "fmt" "io/ioutil" "net/http" + "sort" + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -400,7 +403,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque for _, bid := range seatBid.Bid { bidType, ok := impMediaType[bid.ImpID] if !ok { - errs = append(errs, fmt.Errorf("Unmatched impression id: %s.", bid.ImpID)) + errs = append(errs, fmt.Errorf("unmatched impression id: %s", bid.ImpID)) } var bidExtVideo *openrtb_ext.ExtBidPrebidVideo @@ -417,6 +420,28 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque } } + var bidNative1v1 *Native11Wrapper + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v1) + if err == nil && len(bidNative1v1.Native.EventTrackers) > 0 { + mergeNativeImpTrackers(&bidNative1v1.Native) + if json, err := json.Marshal(bidNative1v1); err == nil { + bid.AdM = string(json) + } + } + } + + var bidNative1v2 *native1response.Response + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v2) + if err == nil && len(bidNative1v2.EventTrackers) > 0 { + mergeNativeImpTrackers(bidNative1v2) + if json, err := json.Marshal(bidNative1v2); err == nil { + bid.AdM = string(json) + } + } + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, @@ -444,3 +469,33 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } return bidder, nil } + +// native 1.2 to 1.1 tracker compatibility handling + +type Native11Wrapper struct { + Native native1response.Response `json:"native,omitempty"` +} + +func mergeNativeImpTrackers(bidNative *native1response.Response) { + + // create unique list of imp pixels urls from `imptrackers` and `eventtrackers` + uniqueImpPixels := map[string]struct{}{} + for _, v := range bidNative.ImpTrackers { + uniqueImpPixels[v] = struct{}{} + } + + for _, v := range bidNative.EventTrackers { + if v.Event == native1.EventTypeImpression && v.Method == native1.EventTrackingMethodImage { + uniqueImpPixels[v.URL] = struct{}{} + } + } + + // rewrite `imptrackers` with new deduped list of imp pixels + bidNative.ImpTrackers = make([]string, 0) + for k := range uniqueImpPixels { + bidNative.ImpTrackers = append(bidNative.ImpTrackers, k) + } + + // sort so tests pass correctly + sort.Strings(bidNative.ImpTrackers) +} diff --git a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json new file mode 100644 index 00000000000..36a239987a6 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"eventtrackers\":[{\"url\":\"https://example.com/imp-2.gif\",\"event\":1,\"method\":1},{\"url\":\"https://example.com/imp-3.gif\",\"event\":1,\"method\":1}],\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\",\"https://example.com/imp-3.gif\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-2.gif\"},{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-3.gif\"}]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/bad-imp-id.json b/adapters/ix/ixtest/supplemental/bad-imp-id.json index 0b852c85d2b..1ca053b674e 100644 --- a/adapters/ix/ixtest/supplemental/bad-imp-id.json +++ b/adapters/ix/ixtest/supplemental/bad-imp-id.json @@ -111,7 +111,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "Unmatched impression id: bad-imp-id.", + "value": "unmatched impression id: bad-imp-id", "comparison": "literal" } ] diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json new file mode 100644 index 00000000000..4cf314e742f --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json new file mode 100644 index 00000000000..d8c78a5cbca --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-missing.json b/adapters/ix/ixtest/supplemental/native-missing.json new file mode 100644 index 00000000000..ec2108ce5d1 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} From 026e64ac0f437c4af5ca83d6e0b4a9a48d0dd451 Mon Sep 17 00:00:00 2001 From: armon823 <86739148+armon823@users.noreply.github.com> Date: Wed, 7 Jul 2021 09:13:06 -0700 Subject: [PATCH 452/603] Inmobi: user sync (#1911) --- adapters/inmobi/usersync.go | 12 ++++++++++++ config/config.go | 1 + static/bidder-info/inmobi.yaml | 2 +- usersync/usersyncers/syncer.go | 2 ++ usersync/usersyncers/syncer_test.go | 2 +- 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 adapters/inmobi/usersync.go diff --git a/adapters/inmobi/usersync.go b/adapters/inmobi/usersync.go new file mode 100644 index 00000000000..7f022e3c5d0 --- /dev/null +++ b/adapters/inmobi/usersync.go @@ -0,0 +1,12 @@ +package inmobi + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewInmobiSyncer(template *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("inmobi", template, adapters.SyncTypeRedirect) +} diff --git a/config/config.go b/config/config.go index f6fa9917178..34d941bc3ea 100644 --- a/config/config.go +++ b/config/config.go @@ -611,6 +611,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderInMobi, "https://id5-sync.com/i/495/0.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dinmobi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BID5UID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") // openrtb_ext.BidderInvibes doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 9b11640f262..634c03481de 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,5 +1,5 @@ maintainer: - email: "prebid-support@inmobi.com" + email: "technology-irv@inmobi.com" gvlVendorID: 333 capabilities: app: diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7472bf3a70d..53472018e30 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -49,6 +49,7 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" @@ -150,6 +151,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderGrid, grid.NewGridSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderGumGum, gumgum.NewGumGumSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderInMobi, inmobi.NewInmobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 81216b19199..35ceb1b70b5 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -58,6 +58,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderGrid): syncConfig, string(openrtb_ext.BidderGumGum): syncConfig, string(openrtb_ext.BidderImprovedigital): syncConfig, + string(openrtb_ext.BidderInMobi): syncConfig, string(openrtb_ext.BidderInvibes): syncConfig, string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderJixie): syncConfig, @@ -120,7 +121,6 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderBidsCube: true, openrtb_ext.BidderEpom: true, openrtb_ext.BidderDecenterAds: true, - openrtb_ext.BidderInMobi: true, openrtb_ext.BidderInteractiveoffers: true, openrtb_ext.BidderKayzen: true, openrtb_ext.BidderKidoz: true, From bc2c8a575c55a2f0279dbefb959688d4c953c67e Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 8 Jul 2021 18:23:54 +0300 Subject: [PATCH 453/603] Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 81 ++++++---- adapters/rubicon/rubicon_test.go | 28 ++++ .../rubicontest/exemplary/simple-video.json | 153 +++++++++++++++--- 3 files changed, 210 insertions(+), 52 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 84d8596449c..579af9839c7 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -91,8 +91,8 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } -type rubiconUserDataExt struct { - TaxonomyName string `json:"taxonomyname"` +type rubiconDataExt struct { + SegTax int `json:"segtax"` } type rubiconUserExt struct { @@ -104,7 +104,8 @@ type rubiconUserExt struct { } type rubiconSiteExtRP struct { - SiteID int `json:"site_id"` + SiteID int `json:"site_id"` + Target json.RawMessage `json:"target,omitempty"` } type rubiconSiteExt struct { @@ -755,12 +756,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if request.User != nil { userCopy := *request.User - userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} - if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil { + target, err := updateExtWithIabAttribute(rubiconExt.Visitor, userCopy.Data, []int{4}) + if err != nil { errs = append(errs, err) continue } + userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} if request.User.Ext != nil { var userExt *openrtb_ext.ExtUser @@ -845,19 +847,30 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada thisImp.Video = nil } - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}} if request.Site != nil { siteCopy := *request.Site - siteCopy.Ext, err = json.Marshal(&siteExt) + siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} + target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = target + + siteCopy.Ext, err = json.Marshal(&siteExtRP) + if err != nil { + errs = append(errs, err) + continue + } + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.Site = &siteCopy - } - if request.App != nil { + } else { appCopy := *request.App - appCopy.Ext, err = json.Marshal(&siteExt) + appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}) appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy @@ -904,41 +917,53 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s return bidFloor, bidFloorCur } -func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { +func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { + var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + + extRPTarget := make(map[string]interface{}) + + if target != nil { + if err := json.Unmarshal(target, &extRPTarget); err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + } + + extRPTarget["iab"] = segmentIdsToCopy + + jsonTarget, err := json.Marshal(&extRPTarget) + if err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + return jsonTarget, nil +} + +func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { var segmentIdsToCopy = make([]string, 0) for _, dataRecord := range data { if dataRecord.Ext != nil { - var dataExtObject rubiconUserDataExt + var dataExtObject rubiconDataExt err := json.Unmarshal(dataRecord.Ext, &dataExtObject) if err != nil { continue } - if strings.EqualFold(dataExtObject.TaxonomyName, "iab") { + if contains(segTaxValues, dataExtObject.SegTax) { for _, segment := range dataRecord.Segment { segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) } } } } + return segmentIdsToCopy +} - userExtRPTarget := make(map[string]interface{}) - - if userExtRP.RP.Target != nil { - if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} +func contains(s []int, e int) bool { + for _, a := range s { + if a == e { + return true } } - - userExtRPTarget["iab"] = segmentIdsToCopy - - if target, err := json.Marshal(&userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } else { - userExtRP.RP.Target = target - } - - return nil + return false } func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 3674c872d73..76904a42137 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1035,6 +1035,10 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, @@ -1146,6 +1150,10 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { "visitor": {"key2" : "val2"} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1195,6 +1203,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1250,6 +1262,10 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1283,6 +1299,10 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { @@ -1391,6 +1411,10 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1436,6 +1460,10 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 408cbb7979d..11afdd50d2b 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -9,11 +9,50 @@ "id": "1", "bundle": "com.wls.testwlsapplication" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -23,7 +62,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -33,7 +72,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -43,13 +92,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -100,11 +149,69 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + }, + "ext": { + "rp": { + "site_id": 113932, + "target": { + "iab": [ + "segmentId1", + "segmentId2", + "segmentId3" + ] + } + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -114,7 +221,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -124,7 +231,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -134,13 +251,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -158,18 +275,6 @@ }, "app": { "id": "1", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - }, "bundle": "com.wls.testwlsapplication" }, "imp": [ From e7f7b558951684f1fb75bc4c180506cdc91b5f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Fern=C3=A1ndez?= Date: Thu, 8 Jul 2021 19:29:46 +0200 Subject: [PATCH 454/603] New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments --- adapters/axonix/axonix.go | 116 +++++++++++++ adapters/axonix/axonix_test.go | 30 ++++ .../exemplary/banner-and-video.json | 133 +++++++++++++++ .../exemplary/banner-video-native.json | 157 ++++++++++++++++++ .../axonixtest/exemplary/simple-banner.json | 105 ++++++++++++ .../axonixtest/exemplary/simple-video.json | 86 ++++++++++ .../axonix/axonixtest/params/race/banner.json | 3 + .../axonix/axonixtest/params/race/video.json | 3 + .../supplemental/bad-response-no-body.json | 57 +++++++ .../supplemental/status-bad-request.json | 58 +++++++ .../supplemental/status-no-content.json | 53 ++++++ .../supplemental/unexpected-status-code.json | 58 +++++++ .../supplemental/valid-extension.json | 86 ++++++++++ .../supplemental/valid-with-device.json | 93 +++++++++++ adapters/axonix/params_test.go | 59 +++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_axonix.go | 5 + static/bidder-info/axonix.yaml | 15 ++ static/bidder-params/axonix.json | 14 ++ usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1137 insertions(+) create mode 100644 adapters/axonix/axonix.go create mode 100644 adapters/axonix/axonix_test.go create mode 100644 adapters/axonix/axonixtest/exemplary/banner-and-video.json create mode 100644 adapters/axonix/axonixtest/exemplary/banner-video-native.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-banner.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-video.json create mode 100644 adapters/axonix/axonixtest/params/race/banner.json create mode 100644 adapters/axonix/axonixtest/params/race/video.json create mode 100644 adapters/axonix/axonixtest/supplemental/bad-response-no-body.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-bad-request.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-no-content.json create mode 100644 adapters/axonix/axonixtest/supplemental/unexpected-status-code.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-extension.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-with-device.json create mode 100644 adapters/axonix/params_test.go create mode 100644 openrtb_ext/imp_axonix.go create mode 100644 static/bidder-info/axonix.yaml create mode 100644 static/bidder-params/axonix.json diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go new file mode 100644 index 00000000000..d3016235319 --- /dev/null +++ b/adapters/axonix/axonix.go @@ -0,0 +1,116 @@ +package axonix + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + URI string +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + var axonixExt openrtb_ext.ExtImpAxonix + if err := json.Unmarshal(bidderExt.Bidder, &axonixExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + thisURI := a.URI + if len(thisURI) == 0 { + thisURI = "https://openrtb-us-east-1.axonix.com/supply/prebid-server/" + axonixExt.SupplyId + } + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: thisURI, + Body: requestJSON, + Headers: headers, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + b := &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaType(bid.ImpID, request.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func getMediaType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impId { + if imp.Native != nil { + return openrtb_ext.BidTypeNative + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go new file mode 100644 index 00000000000..6c4a3eb34d6 --- /dev/null +++ b/adapters/axonix/axonix_test.go @@ -0,0 +1,30 @@ +package axonix + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamplesWithConfiguredURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{ + Endpoint: "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} + +func TestJsonSamplesWithHardcodedURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-and-video.json b/adapters/axonix/axonixtest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..1755cd0ef22 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-and-video.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-video-native.json b/adapters/axonix/axonixtest/exemplary/banner-video-native.json new file mode 100644 index 00000000000..3944eb358b9 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-video-native.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-banner.json b/adapters/axonix/axonixtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..581e59b9b9e --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-video.json b/adapters/axonix/axonixtest/exemplary/simple-video.json new file mode 100644 index 00000000000..c15d7876470 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-video.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/params/race/banner.json b/adapters/axonix/axonixtest/params/race/banner.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/params/race/video.json b/adapters/axonix/axonixtest/params/race/video.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json new file mode 100644 index 00000000000..f89f40f122d --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-bad-request.json b/adapters/axonix/axonixtest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..d64a855e348 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-bad-request.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-no-content.json b/adapters/axonix/axonixtest/supplemental/status-no-content.json new file mode 100644 index 00000000000..96dd899c1fb --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-no-content.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..e7a7be7847e --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/valid-extension.json b/adapters/axonix/axonixtest/supplemental/valid-extension.json new file mode 100644 index 00000000000..372f24d4f76 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-extension.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/valid-with-device.json b/adapters/axonix/axonixtest/supplemental/valid-with-device.json new file mode 100644 index 00000000000..62f4ea06b5a --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-with-device.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go new file mode 100644 index 00000000000..e9c0cc5b83e --- /dev/null +++ b/adapters/axonix/params_test.go @@ -0,0 +1,59 @@ +package axonix + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/axonix.json +// +// These also validate the format of the external API: request.imp[i].ext.axonix + +// TestValidParams makes sure that the Axonix schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAxonix, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Axonix params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Axonix schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAxonix, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8"}`, + `{"supplyId": "test"}`, +} + +var invalidParams = []string{ + `{"supplyId": 100}`, + `{"supplyId": false}`, + `{"supplyId": true}`, + `{"supplyId": 123}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/config/config.go b/config/config.go index 34d941bc3ea..bc00a88fdb7 100644 --- a/config/config.go +++ b/config/config.go @@ -840,6 +840,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.audiencenetwork.disabled", true) v.SetDefault("adapters.audiencenetwork.endpoint", "https://an.facebook.com/placementbid.ortb") v.SetDefault("adapters.avocet.disabled", true) + v.SetDefault("adapters.axonix.disabled", true) v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 17e0d4f54eb..9adc9ebc671 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -29,6 +29,7 @@ import ( "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" "github.com/prebid/prebid-server/adapters/avocet" + "github.com/prebid/prebid-server/adapters/axonix" "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" @@ -152,6 +153,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAppnexus: appnexus.Builder, openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, openrtb_ext.BidderAvocet: avocet.Builder, + openrtb_ext.BidderAxonix: axonix.Builder, openrtb_ext.BidderBeachfront: beachfront.Builder, openrtb_ext.BidderBeintoo: beintoo.Builder, openrtb_ext.BidderBetween: between.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f2dd9815e8e..780b75f60b8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -100,6 +100,7 @@ const ( BidderAppnexus BidderName = "appnexus" BidderAudienceNetwork BidderName = "audienceNetwork" BidderAvocet BidderName = "avocet" + BidderAxonix BidderName = "axonix" BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" @@ -223,6 +224,7 @@ func CoreBidderNames() []BidderName { BidderAppnexus, BidderAudienceNetwork, BidderAvocet, + BidderAxonix, BidderBeachfront, BidderBeintoo, BidderBetween, diff --git a/openrtb_ext/imp_axonix.go b/openrtb_ext/imp_axonix.go new file mode 100644 index 00000000000..7dd9f68418d --- /dev/null +++ b/openrtb_ext/imp_axonix.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAxonix struct { + SupplyId string `json:"supplyId"` +} diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml new file mode 100644 index 00000000000..3c73501d9cc --- /dev/null +++ b/static/bidder-info/axonix.yaml @@ -0,0 +1,15 @@ +maintainer: + email: support.axonix@emodoinc.com +gvlVendorID: 678 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/axonix.json b/static/bidder-params/axonix.json new file mode 100644 index 00000000000..7a3762ce5e2 --- /dev/null +++ b/static/bidder-params/axonix.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Axonix Adapter Params", + "description": "A schema which validates params accepted by the Axonix adapter", + "type": "object", + "properties": { + "supplyId": { + "type": "string", + "minLength": 1, + "description": "Unique supply identifier" + } + }, + "required": ["supplyId"] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 35ceb1b70b5..59ed67cca0b 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -117,6 +117,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdprime: true, openrtb_ext.BidderAlgorix: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderAxonix: true, openrtb_ext.BidderBidmachine: true, openrtb_ext.BidderBidsCube: true, openrtb_ext.BidderEpom: true, From e87bec420e8521c278a38840e1d99dce464e8499 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 9 Jul 2021 11:44:09 -0400 Subject: [PATCH 455/603] Rubicon: Fix Nil Reference Panic (#1918) --- adapters/rubicon/rubicon.go | 17 +- .../supplemental/no-site-content-data.json | 293 ++++++++++++++++++ .../supplemental/no-site-content.json | 289 +++++++++++++++++ 3 files changed, 593 insertions(+), 6 deletions(-) create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content-data.json create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 579af9839c7..84200431992 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -852,12 +852,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if request.Site != nil { siteCopy := *request.Site siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} - target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) - if err != nil { - errs = append(errs, err) - continue + if siteCopy.Content != nil { + target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = target } - siteExtRP.RP.Target = target siteCopy.Ext, err = json.Marshal(&siteExtRP) if err != nil { @@ -919,6 +921,9 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + if len(segmentIdsToCopy) == 0 { + return target, nil + } extRPTarget := make(map[string]interface{}) @@ -938,7 +943,7 @@ func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, seg } func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { - var segmentIdsToCopy = make([]string, 0) + var segmentIdsToCopy = make([]string, 0, len(data)) for _, dataRecord := range data { if dataRecord.Ext != nil { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json new file mode 100644 index 00000000000..f67788a3154 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -0,0 +1,293 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + "content": { + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "ext": { + "bidder": { + "video": { + }, + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "content": { + }, + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ], + "ext": { + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "ext": { + "rp": { + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json new file mode 100644 index 00000000000..d3b8f8b7454 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -0,0 +1,289 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "ext": { + "bidder": { + "video": { + }, + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ], + "ext": { + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "ext": { + "rp": { + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} From aff5f70d8fee97d4641f8686c2a34d91a56385b4 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:51:46 -0400 Subject: [PATCH 456/603] GDPR: host-level per-purpose vendor exceptions config (#1893) Co-authored-by: Scott Kay --- config/config.go | 67 ++++++++++++++-- config/config_test.go | 89 +++++++++++++++++++++ gdpr/gdpr.go | 15 ++++ gdpr/impl.go | 45 ++++++++--- gdpr/impl_test.go | 176 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 361 insertions(+), 31 deletions(-) diff --git a/config/config.go b/config/config.go index bc00a88fdb7..96e3c1f1e65 100644 --- a/config/config.go +++ b/config/config.go @@ -240,20 +240,30 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { - Enabled bool `mapstructure:"enabled"` - Purpose1 PurposeDetail `mapstructure:"purpose1"` - Purpose2 PurposeDetail `mapstructure:"purpose2"` - Purpose7 PurposeDetail `mapstructure:"purpose7"` - SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` - PurposeOneTreatment PurposeOneTreatment `mapstructure:"purpose_one_treatment"` + Enabled bool `mapstructure:"enabled"` + Purpose1 TCF2Purpose `mapstructure:"purpose1"` + Purpose2 TCF2Purpose `mapstructure:"purpose2"` + Purpose3 TCF2Purpose `mapstructure:"purpose3"` + Purpose4 TCF2Purpose `mapstructure:"purpose4"` + Purpose5 TCF2Purpose `mapstructure:"purpose5"` + Purpose6 TCF2Purpose `mapstructure:"purpose6"` + Purpose7 TCF2Purpose `mapstructure:"purpose7"` + Purpose8 TCF2Purpose `mapstructure:"purpose8"` + Purpose9 TCF2Purpose `mapstructure:"purpose9"` + Purpose10 TCF2Purpose `mapstructure:"purpose10"` + SpecialPurpose1 TCF2Purpose `mapstructure:"special_purpose1"` + PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } // Making a purpose struct so purpose specific details can be added later. -type PurposeDetail struct { +type TCF2Purpose struct { Enabled bool `mapstructure:"enabled"` + // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed + VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` + VendorExceptionMap map[openrtb_ext.BidderName]struct{} } -type PurposeOneTreatment struct { +type TCF2PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -503,6 +513,30 @@ func New(v *viper.Viper) (*Configuration, error) { c.GDPR.NonStandardPublisherMap[c.GDPR.EEACountries[i]] = s } + // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table located in the + // VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file + purposeConfigs := []*TCF2Purpose{ + &c.GDPR.TCF2.Purpose1, + &c.GDPR.TCF2.Purpose2, + &c.GDPR.TCF2.Purpose3, + &c.GDPR.TCF2.Purpose4, + &c.GDPR.TCF2.Purpose5, + &c.GDPR.TCF2.Purpose6, + &c.GDPR.TCF2.Purpose7, + &c.GDPR.TCF2.Purpose8, + &c.GDPR.TCF2.Purpose9, + &c.GDPR.TCF2.Purpose10, + &c.GDPR.TCF2.SpecialPurpose1, + } + for c := 0; c < len(purposeConfigs); c++ { + purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) + + for v := 0; v < len(purposeConfigs[c].VendorExceptions); v++ { + bidderName := purposeConfigs[c].VendorExceptions[v] + purposeConfigs[c].VendorExceptionMap[bidderName] = struct{}{} + } + } + // To look for a request's app_id in O(1) time, we fill this hash table located in the // the BlacklistedApps field of the Configuration struct defined in this file c.BlacklistedAppMap = make(map[string]bool) @@ -957,9 +991,26 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) v.SetDefault("gdpr.tcf2.purpose2.enabled", true) + v.SetDefault("gdpr.tcf2.purpose3.enabled", true) v.SetDefault("gdpr.tcf2.purpose4.enabled", true) + v.SetDefault("gdpr.tcf2.purpose5.enabled", true) + v.SetDefault("gdpr.tcf2.purpose6.enabled", true) v.SetDefault("gdpr.tcf2.purpose7.enabled", true) + v.SetDefault("gdpr.tcf2.purpose8.enabled", true) + v.SetDefault("gdpr.tcf2.purpose9.enabled", true) + v.SetDefault("gdpr.tcf2.purpose10.enabled", true) + v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose4.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose5.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose6.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose7.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) + v.SetDefault("gdpr.tcf2.special_purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", diff --git a/config/config_test.go b/config/config_test.go index fa9dcdc5195..c33a345c473 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -147,6 +147,30 @@ gdpr: host_vendor_id: 15 default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] + tcf2: + purpose1: + vendor_exceptions: ["foo1a", "foo1b"] + purpose2: + enabled: false + vendor_exceptions: ["foo2"] + purpose3: + vendor_exceptions: ["foo3"] + purpose4: + vendor_exceptions: ["foo4"] + purpose5: + vendor_exceptions: ["foo5"] + purpose6: + vendor_exceptions: ["foo6"] + purpose7: + vendor_exceptions: ["foo7"] + purpose8: + vendor_exceptions: ["foo8"] + purpose9: + vendor_exceptions: ["foo9"] + purpose10: + vendor_exceptions: ["foo10"] + special_purpose1: + vendor_exceptions: ["fooSP1"] ccpa: enforce: true lmt: @@ -378,6 +402,71 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "cfg.BlacklistedAppMap", cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]], true) } + //Assert purpose VendorExceptionMap hash tables were built correctly + expectedTCF2 := TCF2{ + Enabled: true, + Purpose1: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, + }, + Purpose2: TCF2Purpose{ + Enabled: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, + }, + Purpose3: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, + }, + Purpose4: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, + }, + Purpose5: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, + }, + Purpose6: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, + }, + Purpose7: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, + }, + Purpose8: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, + }, + Purpose9: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, + }, + Purpose10: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, + }, + SpecialPurpose1: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("fooSP1")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("fooSP1"): {}}, + }, + PurposeOneTreatment: TCF2PurposeOneTreatment{ + Enabled: true, // true by default + AccessAllowed: true, // true by default + }, + } + assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") + cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "recaptcha_secret", cfg.RecaptchaSecret, "asdfasdfasdfasdf") diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 47d20a50899..d2d282b2fec 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -5,6 +5,7 @@ import ( "net/http" "strconv" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -44,9 +45,23 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ gdprDefaultValue = SignalNo } + purposeConfigs := map[consentconstants.Purpose]config.TCF2Purpose{ + 1: cfg.TCF2.Purpose1, + 2: cfg.TCF2.Purpose2, + 3: cfg.TCF2.Purpose3, + 4: cfg.TCF2.Purpose4, + 5: cfg.TCF2.Purpose5, + 6: cfg.TCF2.Purpose6, + 7: cfg.TCF2.Purpose7, + 8: cfg.TCF2.Purpose8, + 9: cfg.TCF2.Purpose9, + 10: cfg.TCF2.Purpose10, + } + permissionsImpl := &permissionsImpl{ cfg: cfg, gdprDefaultValue: gdprDefaultValue, + purposeConfigs: purposeConfigs, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, diff --git a/gdpr/impl.go b/gdpr/impl.go index 17a1a893e1c..ac0bfefdba4 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -30,6 +30,7 @@ const ( type permissionsImpl struct { cfg config.GDPR gdprDefaultValue Signal + purposeConfigs map[consentconstants.Purpose]config.TCF2Purpose vendorIDs map[openrtb_ext.BidderName]uint16 fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } @@ -41,7 +42,7 @@ func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Sig return true, nil } - return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent) + return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent, false) } func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { @@ -53,7 +54,8 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ id, ok := p.vendorIDs[bidder] if ok { - return p.allowSync(ctx, id, consent) + vendorException := p.isVendorException(consentconstants.Purpose(1), bidder) + return p.allowSync(ctx, id, consent, vendorException) } return false, nil @@ -80,9 +82,9 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowActivities(ctx, id, consent, weakVendorEnforcement) + return p.allowActivities(ctx, id, bidder, consent, weakVendorEnforcement) } else if weakVendorEnforcement { - return p.allowActivities(ctx, 0, consent, weakVendorEnforcement) + return p.allowActivities(ctx, 0, bidder, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() @@ -104,7 +106,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return SignalYes } -func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) { +func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string, vendorException bool) (bool, error) { if consent == "" { return false, nil @@ -127,10 +129,10 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen err := fmt.Errorf("Unable to access TCF2 parsed consent") return false, err } - return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, false), nil + return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil } -func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { +func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { return false, false, false, err @@ -156,17 +158,20 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, } if p.cfg.TCF2.SpecialPurpose1.Enabled { - passGeo = consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + vendorException := p.isSpecialPurposeVendorException(bidder) + passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement)) } else { passGeo = true } if p.cfg.TCF2.Purpose2.Enabled { - allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), weakVendorEnforcement) + vendorException := p.isVendorException(consentconstants.Purpose(2), bidder) + allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement) } else { allowBidRequest = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), weakVendorEnforcement) { + vendorException := p.isVendorException(consentconstants.Purpose(i), bidder) + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), vendorException, weakVendorEnforcement) { passID = true break } @@ -175,11 +180,25 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, return } +func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.purposeConfigs[purpose].VendorExceptionMap[bidder]; ok { + vendorException = true + } + return +} + +func (p *permissionsImpl) isSpecialPurposeVendorException(bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.cfg.TCF2.SpecialPurpose1.VendorExceptionMap[bidder]; ok { + vendorException = true + } + return +} + const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, vendorException, weakVendorEnforcement bool) bool { if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } @@ -187,6 +206,10 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return false } + if vendorException { + return true + } + purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index cde485467b3..93d23c1acf6 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" @@ -64,7 +65,7 @@ func TestAllowedSyncs(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -104,7 +105,7 @@ func TestProhibitedPurposes(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -143,7 +144,7 @@ func TestProhibitedVendors(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -287,7 +288,7 @@ func TestAllowActivities(t *testing.T) { NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}}, TCF2: config.TCF2{ Enabled: true, - Purpose2: config.PurposeDetail{ + Purpose2: config.TCF2Purpose{ Enabled: true, }, }, @@ -360,10 +361,10 @@ var gdprConfig = config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.PurposeDetail{Enabled: true}, - Purpose2: config.PurposeDetail{Enabled: true}, - Purpose7: config.PurposeDetail{Enabled: true}, - SpecialPurpose1: config.PurposeDetail{Enabled: true}, + Purpose1: config.TCF2Purpose{Enabled: true}, + Purpose2: config.TCF2Purpose{Enabled: true}, + Purpose7: config.TCF2Purpose{Enabled: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true}, }, } @@ -796,10 +797,10 @@ func TestAllowActivitiesBidRequests(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.PurposeDetail{Enabled: true}, - Purpose2: config.PurposeDetail{Enabled: td.purpose2Enabled}, - Purpose7: config.PurposeDetail{Enabled: true}, - SpecialPurpose1: config.PurposeDetail{Enabled: true}, + Purpose1: config.TCF2Purpose{Enabled: true}, + Purpose2: config.TCF2Purpose{Enabled: td.purpose2Enabled}, + Purpose7: config.TCF2Purpose{Enabled: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true}, }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -837,3 +838,154 @@ func TestTCF1Consent(t *testing.T) { assert.Equal(t, false, passGeo, "TCF1 consent - passing geo not allowed") assert.Equal(t, false, passID, "TCF1 consent - passing id not allowed") } + +func TestAllowActivitiesVendorException(t *testing.T) { + noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA" + noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" + + testDefs := []struct { + description string + p2VendorExceptionMap map[openrtb_ext.BidderName]struct{} + sp1VendorExceptionMap map[openrtb_ext.BidderName]struct{} + bidder openrtb_ext.BidderName + consent string + allowBid bool + passGeo bool + passID bool + }{ + { + description: "Bid/ID blocked by publisher - p2 enabled with p2 vendor exception, pub restricts p2 for vendor", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsP2, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid/ID allowed by vendor exception - p2 enabled with p2 vendor exception, pub restricts none", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Geo blocked - sp1 enabled but no consent", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Geo allowed by vendor exception - sp1 enabled with sp1 vendor exception", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: false, + passGeo: true, + passID: false, + }, + } + + for _, td := range testDefs { + vendorListData := MarshalVendorList(buildVendorList34()) + perms := permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose2: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p2VendorExceptionMap}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap}, + }, + }, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 32, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3, + } + + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, false) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) + } +} + +func TestBidderSyncAllowedVendorException(t *testing.T) { + noPurposeOrVendorConsentAndPubRestrictsP1 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAAQAAAAAAAAAAIIACACA" + noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" + + testDefs := []struct { + description string + p1VendorExceptionMap map[openrtb_ext.BidderName]struct{} + bidder openrtb_ext.BidderName + consent string + allowSync bool + }{ + { + description: "Sync blocked by no consent - p1 enabled, no p1 vendor exception, pub restricts none", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowSync: false, + }, + { + description: "Sync blocked by publisher - p1 enabled with p1 vendor exception, pub restricts p1 for vendor", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsP1, + allowSync: false, + }, + { + description: "Sync allowed by vendor exception - p1 enabled with p1 vendor exception, pub restricts none", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowSync: true, + }, + } + + for _, td := range testDefs { + vendorListData := MarshalVendorList(buildVendorList34()) + perms := permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p1VendorExceptionMap}, + }, + }, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 32, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } + + allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder, SignalYes, td.consent) + assert.NoErrorf(t, err, "Error processing BidderSyncAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowSync, allowSync, "AllowSync failure on %s", td.description) + } +} From e37b1a672537175c77726c0ccae59ead9c5f15cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Wed, 14 Jul 2021 22:06:29 +0200 Subject: [PATCH 457/603] Criteo - Fix fields mapping error when building bid from bidder response (#1917) --- adapters/criteo/criteo.go | 4 +- .../exemplary/simple-banner-cookie-uid.json | 230 ++++----- .../exemplary/simple-banner-inapp.json | 220 ++++----- .../exemplary/simple-banner-uid.json | 264 +++++----- ...ccpa-with-consent-simple-banner-inapp.json | 229 +++++---- .../ccpa-with-consent-simple-banner-uid.json | 277 ++++++----- ...gdpr-with-consent-simple-banner-inapp.json | 251 +++++----- .../gdpr-with-consent-simple-banner-uid.json | 283 ++++++----- .../supplemental/multislots-alt-case.json | 12 +- .../multislots-simple-banner-inapp.json | 425 ++++++++-------- .../multislots-simple-banner-uid.json | 465 +++++++++--------- ...-direct-size-and-formats-not-included.json | 264 +++++----- ...e-banner-with-direct-size-and-formats.json | 264 +++++----- .../simple-banner-with-direct-size.json | 252 +++++----- .../supplemental/simple-banner-with-ipv6.json | 252 +++++----- adapters/criteo/models.go | 20 +- 16 files changed, 1853 insertions(+), 1859 deletions(-) diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index 9f6ca4d74ec..de5568b9b9b 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -86,13 +86,13 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest for i := 0; i < len(bidResponse.Slots); i++ { bidderResponse.Bids[i] = &adapters.TypedBid{ Bid: &openrtb2.Bid{ - ID: bidResponse.Slots[i].ID, + ID: bidResponse.Slots[i].ArbitrageID, ImpID: bidResponse.Slots[i].ImpID, Price: bidResponse.Slots[i].CPM, AdM: bidResponse.Slots[i].Creative, W: bidResponse.Slots[i].Width, H: bidResponse.Slots[i].Height, - CrID: bidResponse.Slots[i].CreativeID, + CrID: bidResponse.Slots[i].CreativeCode, }, BidType: openrtb_ext.BidTypeBanner, } diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json index cb152f47c41..cb1179501ce 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json @@ -1,115 +1,115 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "buyeruid": "criteo-user-id" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"], - "Cookie": ["uid=criteo-user-id"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "cookieuid": "criteo-user-id", - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "criteo-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"], + "Cookie": ["uid=criteo-user-id"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "cookieuid": "criteo-user-id", + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json index 0ec1b7f87a5..3dec57ffc46 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json @@ -1,110 +1,110 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json index 2499790bf5a..ea453deee2c 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json index b047fa183d3..febc3e7d16a 100755 --- a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json @@ -1,115 +1,114 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "ip": "91.199.242.236", - "ua": "random user agent", - "os": "android" - }, - "regs": { - "ext": { - "us_privacy": "1YYY" - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "uspIab": "1YYY", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json index f839624af1e..4046b641785 100755 --- a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json @@ -1,139 +1,138 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "regs": { - "ext": { - "us_privacy": "1YYY" - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid", - "uspIab": "1YYY" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json index 416053dbc67..5bb5f98bec9 100755 --- a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json @@ -1,126 +1,125 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "consent": "iabconsentstringwithconsent" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": { - "consentdata": "iabconsentstringwithconsent", - "gdprapplies": true - }, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json index e8374616db3..ea64fb0c122 100755 --- a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json @@ -1,142 +1,141 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "consent": "iabconsentstringwithconsent", - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": { - "consentdata": "iabconsentstringwithconsent", - "gdprapplies": true - }, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent", + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json index beb855e3f2b..cb7022c0404 100644 --- a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json +++ b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json @@ -146,7 +146,7 @@ "body": { "slots": [ { - "id": "test-slot-id-1", + "arbitrageid": "test-slot-id-1", "impid": "test-imp-id-1", "zoneid": 123456, "networkid": 78910, @@ -154,11 +154,11 @@ "currency": "USD", "width": 300, "height": 250, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" }, { - "id": "test-slot-id-2", + "arbitrageid": "test-slot-id-2", "impid": "test-imp-id-2", "zoneid": 7891011, "networkid": 78910, @@ -166,11 +166,11 @@ "currency": "USD", "width": 320, "height": 50, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" }, { - "id": "test-slot-id-3", + "arbitrageid": "test-slot-id-3", "impid": "test-imp-id-3", "zoneid": 121314, "networkid": 78910, @@ -178,7 +178,7 @@ "currency": "USD", "width": 300, "height": 600, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" } ] diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json index 75f4118eb93..2986601342b 100755 --- a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json @@ -1,213 +1,212 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ip": "91.199.242.236", - "ua": "random user agent", - "ifa": "test-ifa-123456", - "os": "android" - }, - "imp": [ - { - "id": "test-imp-id-1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 7891011, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 121314, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "cpm": 0.2, - "currency": "USD", - "width": 320, - "height": 50, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "cpm": 0.3, - "currency": "USD", - "width": 300, - "height": 600, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "price": 0.2, - "crid": "creative-123", - "adm": "", - "w": 320, - "h": 50 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "price": 0.3, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 600 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ifa": "test-ifa-123456", + "os": "android" + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json index e5de75e04c0..913f19b7710 100755 --- a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json @@ -1,233 +1,232 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id-1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 7891011, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 121314, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "cpm": 0.2, - "currency": "USD", - "width": 320, - "height": 50, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "cpm": 0.3, - "currency": "USD", - "width": 300, - "height": 600, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "price": 0.2, - "crid": "creative-123", - "adm": "", - "w": 320, - "h": 50 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "price": 0.3, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 600 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json index a0cc53d00f3..8f421f2b71e 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250, - "format": [ - { - "w": 250, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "250x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 250, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "250x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json index 016c16a866f..cf73d3074cc 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250, - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json index 68e45da9fb3..ae965477b34 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json @@ -1,126 +1,126 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json index e248db9bc30..0aa021d2d6d 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json @@ -1,126 +1,126 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go index 1ae51c12f8b..0365c0e62a6 100644 --- a/adapters/criteo/models.go +++ b/adapters/criteo/models.go @@ -286,14 +286,14 @@ func newCriteoResponseFromBytes(bytes []byte) (criteoResponse, error) { } type criteoResponseSlot struct { - ID string `json:"id,omitempty"` - ImpID string `json:"impid,omitempty"` - ZoneID int64 `json:"zoneid,omitempty"` - NetworkID int64 `json:"networkid,omitempty"` - CPM float64 `json:"cpm,omitempty"` - Currency string `json:"currency,omitempty"` - Width int64 `json:"width,omitempty"` - Height int64 `json:"height,omitempty"` - Creative string `json:"creative,omitempty"` - CreativeID string `json:"creativeid,omitempty"` + ArbitrageID string `json:"arbitrageid,omitempty"` + ImpID string `json:"impid,omitempty"` + ZoneID int64 `json:"zoneid,omitempty"` + NetworkID int64 `json:"networkid,omitempty"` + CPM float64 `json:"cpm,omitempty"` + Currency string `json:"currency,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` + Creative string `json:"creative,omitempty"` + CreativeCode string `json:"creativecode,omitempty"` } From 150be5300d94a75710d69dabe3fb1708f60db184 Mon Sep 17 00:00:00 2001 From: el-chuck Date: Thu, 15 Jul 2021 00:22:48 +0200 Subject: [PATCH 458/603] Smaato: Rework multi imp support and add adpod support (#1902) --- adapters/smaato/image.go | 42 +- adapters/smaato/image_test.go | 59 +- adapters/smaato/params_test.go | 2 + adapters/smaato/richmedia.go | 41 +- adapters/smaato/richmedia_test.go | 53 +- adapters/smaato/smaato.go | 543 +++++++++++------ adapters/smaato/smaato_test.go | 93 +++ .../exemplary/multiple-impressions.json | 354 +++++++++++ .../exemplary/simple-banner-app.json | 5 +- .../simple-banner-richMedia-app.json | 5 +- .../exemplary/simple-banner-richMedia.json | 5 +- .../smaatotest/exemplary/simple-banner.json | 5 +- .../smaatotest/exemplary/video-app.json | 5 +- .../smaato/smaatotest/exemplary/video.json | 5 +- .../smaatotest/params/{ => race}/banner.json | 0 .../smaato/smaatotest/params/race/video.json | 4 + .../supplemental/adtype-header-response.json | 194 ++++++ .../supplemental/bad-adm-response.json | 5 +- .../bad-adtype-header-response.json | 174 ++++++ .../bad-expires-header-response.json | 194 ++++++ .../smaatotest/supplemental/bad-ext-req.json | 54 -- .../bad-imp-banner-format-req.json | 61 -- .../bad-imp-banner-format-request.json | 28 + .../supplemental/bad-media-type-request.json | 27 + .../supplemental/bad-site-ext-request.json | 34 ++ ...400.json => bad-status-code-response.json} | 4 +- ...eq.json => bad-user-ext-data-request.json} | 14 +- .../supplemental/bad-user-ext-request.json | 36 ++ .../supplemental/banner-w-and-h.json | 173 ++++++ .../supplemental/expires-header-response.json | 194 ++++++ ...ta-req.json => no-adspace-id-request.json} | 27 +- .../supplemental/no-app-site-request.json | 30 + ...tus-code-204.json => no-bid-response.json} | 6 +- ...info.json => no-consent-info-request.json} | 5 +- .../{no-imp-req.json => no-imp-request.json} | 7 +- .../supplemental/no-publisher-id-request.json | 29 + .../outdated-expires-header-response.json | 193 ++++++ .../smaatotest/video/multiple-adpods.json | 555 ++++++++++++++++++ .../smaato/smaatotest/video/single-adpod.json | 293 +++++++++ .../bad-adbreak-id-request.json | 90 +++ .../videosupplemental/bad-adm-response.json | 276 +++++++++ .../bad-bid-ext-response.json | 274 +++++++++ .../bad-media-type-request.json | 37 ++ .../bad-publisher-id-request.json | 90 +++ openrtb_ext/imp_smaato.go | 5 +- static/bidder-params/smaato.json | 20 +- 46 files changed, 3926 insertions(+), 424 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-impressions.json rename adapters/smaato/smaatotest/params/{ => race}/banner.json (100%) create mode 100644 adapters/smaato/smaatotest/params/race/video.json create mode 100644 adapters/smaato/smaatotest/supplemental/adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-ext-req.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-400.json => bad-status-code-response.json} (97%) rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-req.json => bad-user-ext-data-request.json} (81%) create mode 100644 adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/banner-w-and-h.json create mode 100644 adapters/smaato/smaatotest/supplemental/expires-header-response.json rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-data-req.json => no-adspace-id-request.json} (65%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-app-site-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-204.json => no-bid-response.json} (96%) rename adapters/smaato/smaatotest/supplemental/{no-consent-info.json => no-consent-info-request.json} (98%) rename adapters/smaato/smaatotest/supplemental/{no-imp-req.json => no-imp-request.json} (59%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json create mode 100644 adapters/smaato/smaatotest/video/multiple-adpods.json create mode 100644 adapters/smaato/smaatotest/video/single-adpod.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go index 582206ccb0c..a4dad157bd1 100644 --- a/adapters/smaato/image.go +++ b/adapters/smaato/image.go @@ -3,6 +3,7 @@ package smaato import ( "encoding/json" "fmt" + "github.com/prebid/prebid-server/errortypes" "net/url" "strings" ) @@ -22,32 +23,27 @@ type img struct { Ctaurl string `json:"ctaurl"` } -func extractAdmImage(adapterResponseAdm string) (string, error) { - var imgMarkup string - var err error - +func extractAdmImage(adMarkup string) (string, error) { var imageAd imageAd - err = json.Unmarshal([]byte(adapterResponseAdm), &imageAd) - var image = imageAd.Image - - if err == nil { - var clickEvent strings.Builder - var impressionTracker strings.Builder - - for _, clicktracker := range image.Clicktrackers { - clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " + - "{cache: 'no-cache'});") + if err := json.Unmarshal([]byte(adMarkup), &imageAd); err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), } + } - for _, impression := range image.Impressiontrackers { - - impressionTracker.WriteString(fmt.Sprintf(``, impression)) - } + var clickEvent strings.Builder + for _, clicktracker := range imageAd.Image.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " + + "{cache: 'no-cache'});") + } - imgMarkup = fmt.Sprintf(`
%s
`, - &clickEvent, url.QueryEscape(image.Img.Ctaurl), image. - Img.URL, image.Img.W, image.Img. - H, &impressionTracker) + var impressionTracker strings.Builder + for _, impression := range imageAd.Image.Impressiontrackers { + impressionTracker.WriteString(fmt.Sprintf(``, impression)) } - return imgMarkup, err + + imageAdMarkup := fmt.Sprintf(`
%s
`, + &clickEvent, url.QueryEscape(imageAd.Image.Img.Ctaurl), imageAd.Image.Img.URL, imageAd.Image.Img.W, imageAd.Image.Img.H, &impressionTracker) + + return imageAdMarkup, nil } diff --git a/adapters/smaato/image_test.go b/adapters/smaato/image_test.go index 5f39c857201..1ba99ddd7c5 100644 --- a/adapters/smaato/image_test.go +++ b/adapters/smaato/image_test.go @@ -1,44 +1,51 @@ package smaato import ( + "github.com/stretchr/testify/assert" "testing" ) -func TestRenderAdMarkup(t *testing.T) { - type args struct { - adType adMarkupType - adapterResponseAdm string - } - expectedResult := `
` + - `` + - `` + - `
` - +func TestExtractAdmImage(t *testing.T) { tests := []struct { - testName string - args args - result string + testName string + adMarkup string + expectedAdMarkup string + expectedError string }{ - {"imageTest", args{"Img", - "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," + + { + testName: "extract image", + adMarkup: "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," + "\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"}," + "\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + - "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, - expectedResult, + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + expectedAdMarkup: `
` + + `` + + `` + + `
`, + expectedError: "", + }, + { + testName: "invalid adMarkup", + adMarkup: "{", + expectedAdMarkup: "", + expectedError: "Invalid ad markup {.", }, } + for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) - if err != nil { - t.Errorf("error rendering ad markup: %v", err) - } - if got != tt.result { - t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + adMarkup, err := extractAdmImage(tt.adMarkup) + + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + } else { + assert.NoError(t, err) } + + assert.Equal(t, tt.expectedAdMarkup, adMarkup) }) } } diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go index 6c71cbe75c6..2e29550a394 100644 --- a/adapters/smaato/params_test.go +++ b/adapters/smaato/params_test.go @@ -41,6 +41,8 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321"}`, + `{"publisherId":"test-id-1234-smaato","adbreakId": "4123581321"}`, + `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321","adbreakId": "4123581321"}`, } var invalidParams = []string{ diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go index 1c94a3555c1..a8865361d38 100644 --- a/adapters/smaato/richmedia.go +++ b/adapters/smaato/richmedia.go @@ -3,6 +3,7 @@ package smaato import ( "encoding/json" "fmt" + "github.com/prebid/prebid-server/errortypes" "net/url" "strings" ) @@ -22,31 +23,27 @@ type richmedia struct { Clicktrackers []string `json:"clicktrackers"` } -func extractAdmRichMedia(adapterResponseAdm string) (string, error) { - var richMediaMarkup string - var err error - +func extractAdmRichMedia(adMarkup string) (string, error) { var richMediaAd richMediaAd - err = json.Unmarshal([]byte(adapterResponseAdm), &richMediaAd) - var richMedia = richMediaAd.RichMedia - - if err == nil { - var clickEvent strings.Builder - var impressionTracker strings.Builder - - for _, clicktracker := range richMedia.Clicktrackers { - clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " + - "{cache: 'no-cache'});") + if err := json.Unmarshal([]byte(adMarkup), &richMediaAd); err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), } - for _, impression := range richMedia.Impressiontrackers { + } - impressionTracker.WriteString(fmt.Sprintf(``, impression)) - } + var clickEvent strings.Builder + var impressionTracker strings.Builder - richMediaMarkup = fmt.Sprintf(`
%s%s
`, - &clickEvent, - richMedia.MediaData.Content, - &impressionTracker) + for _, clicktracker := range richMediaAd.RichMedia.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " + + "{cache: 'no-cache'});") + } + for _, impression := range richMediaAd.RichMedia.Impressiontrackers { + impressionTracker.WriteString(fmt.Sprintf(``, impression)) } - return richMediaMarkup, err + + richmediaAdMarkup := fmt.Sprintf(`
%s%s
`, + &clickEvent, richMediaAd.RichMedia.MediaData.Content, &impressionTracker) + + return richmediaAdMarkup, nil } diff --git a/adapters/smaato/richmedia_test.go b/adapters/smaato/richmedia_test.go index 20fa1ba353c..eff559852be 100644 --- a/adapters/smaato/richmedia_test.go +++ b/adapters/smaato/richmedia_test.go @@ -1,39 +1,48 @@ package smaato import ( + "github.com/stretchr/testify/assert" "testing" ) func TestExtractAdmRichMedia(t *testing.T) { - type args struct { - adType adMarkupType - adapterResponseAdm string - } - expectedResult := `
hello
` + - `
` tests := []struct { - testName string - args args - result string + testName string + adMarkup string + expectedAdMarkup string + expectedError string }{ - {"richmediaTest", args{"Richmedia", "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\"," + - "" + "\"w\":350," + - "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + - "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, - expectedResult, + { + testName: "extract richmedia", + adMarkup: "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\"," + + "" + "\"w\":350," + + "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + expectedAdMarkup: `
hello
` + + `
`, + expectedError: "", + }, + { + testName: "invalid adMarkup", + adMarkup: "{", + expectedAdMarkup: "", + expectedError: "Invalid ad markup {.", }, } + for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) - if err != nil { - t.Errorf("error rendering ad markup: %v", err) - } - if got != tt.result { - t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + adMarkup, err := extractAdmRichMedia(tt.adMarkup) + + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + } else { + assert.NoError(t, err) } + + assert.Equal(t, tt.expectedAdMarkup, adMarkup) }) } } diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 9aea2e1e614..c50efffc994 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "github.com/buger/jsonparser" @@ -11,10 +12,12 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/timeutil" ) -const clientVersion = "prebid_server_0.2" +const clientVersion = "prebid_server_0.3" type adMarkupType string @@ -24,23 +27,20 @@ const ( smtAdTypeVideo adMarkupType = "Video" ) -// SmaatoAdapter describes a Smaato prebid server adapter. -type SmaatoAdapter struct { - URI string -} - -//userExt defines User.Ext object for Smaato -type userExt struct { - Data userExtData `json:"data"` +// adapter describes a Smaato prebid server adapter. +type adapter struct { + clock timeutil.Time + endpoint string } +// userExtData defines User.Ext.Data object for Smaato type userExtData struct { Keywords string `json:"keywords"` Gender string `json:"gender"` Yob int64 `json:"yob"` } -//userExt defines Site.Ext object for Smaato +// siteExt defines Site.Ext object for Smaato type siteExt struct { Data siteExtData `json:"data"` } @@ -49,189 +49,224 @@ type siteExtData struct { Keywords string `json:"keywords"` } +// bidRequestExt defines BidRequest.Ext object for Smaato +type bidRequestExt struct { + Client string `json:"client"` +} + +// bidExt defines Bid.Ext object for Smaato +type bidExt struct { + Duration int `json:"duration"` +} + +// videoExt defines Video.Ext object for Smaato +type videoExt struct { + Context string `json:"context,omitempty"` +} + // Builder builds a new instance of the Smaato adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &SmaatoAdapter{ - URI: config.Endpoint, + bidder := &adapter{ + clock: &timeutil.RealTime{}, + endpoint: config.Endpoint, } return bidder, nil } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmaatoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { - errs = append(errs, &errortypes.BadInput{Message: "no impressions in bid request"}) - return nil, errs + return nil, []error{&errortypes.BadInput{Message: "No impressions in bid request."}} } - // Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId - publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") - if err != nil { - errs = append(errs, err) - return nil, errs + // set data in request that is common for all requests + if err := prepareCommonRequest(request); err != nil { + return nil, []error{err} } - for i := 0; i < len(request.Imp); i++ { - err := parseImpressionObject(&request.Imp[i]) - // If the parsing is failed, remove imp and add the error. - if err != nil { - errs = append(errs, err) - request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) - i-- - } + isVideoEntryPoint := reqInfo.PbsEntryPoint == metrics.ReqTypeVideo + + if isVideoEntryPoint { + return adapter.makePodRequests(request) + } else { + return adapter.makeIndividualRequests(request) } +} - if request.Site != nil { - siteCopy := *request.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} +// MakeBids unpacks the server's response into Bids. +func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } - if request.Site.Ext != nil { - var siteExt siteExt - err := json.Unmarshal([]byte(request.Site.Ext), &siteExt) + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + var errors []error + for _, seatBid := range bidResp.SeatBid { + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + + adMarkupType, err := getAdMarkupType(response, bid.AdM) if err != nil { - errs = append(errs, err) - return nil, errs + errors = append(errors, err) + continue } - siteCopy.Keywords = siteExt.Data.Keywords - siteCopy.Ext = nil + + bid.AdM, err = renderAdMarkup(adMarkupType, bid.AdM) + if err != nil { + errors = append(errors, err) + continue + } + + bidType, err := convertAdMarkupTypeToMediaType(adMarkupType) + if err != nil { + errors = append(errors, err) + continue + } + + bidVideo, err := buildBidVideo(&bid, bidType) + if err != nil { + errors = append(errors, err) + continue + } + + bid.Exp = adapter.getTTLFromHeaderOrDefault(response) + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + BidVideo: bidVideo, + }) } - request.Site = &siteCopy } + return bidResponse, errors +} - if request.App != nil { - appCopy := *request.App - appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} +func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + imps := request.Imp - request.App = &appCopy - } + requests := make([]*adapters.RequestData, 0, len(imps)) + errors := make([]error, 0, len(imps)) - if request.User != nil && request.User.Ext != nil { - var userExt userExt - var userExtRaw map[string]json.RawMessage + for _, imp := range imps { + request.Imp = []openrtb2.Imp{imp} + if err := prepareIndividualRequest(request); err != nil { + errors = append(errors, err) + continue + } - rawExtErr := json.Unmarshal(request.User.Ext, &userExtRaw) - if rawExtErr != nil { - errs = append(errs, rawExtErr) - return nil, errs + requestData, err := adapter.makeRequest(request) + if err != nil { + errors = append(errors, err) + continue } - userExtErr := json.Unmarshal([]byte(request.User.Ext), &userExt) - if userExtErr != nil { - errs = append(errs, userExtErr) - return nil, errs + requests = append(requests, requestData) + } + + return requests, errors +} + +func (adapter *adapter) makePodRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + pods, orderedKeys, errors := groupImpressionsByPod(request.Imp) + requests := make([]*adapters.RequestData, 0, len(pods)) + + for _, key := range orderedKeys { + request.Imp = pods[key] + + if err := preparePodRequest(request); err != nil { + errors = append(errors, err) + continue } - userCopy := *request.User - extractUserExtAttributes(userExt, &userCopy) - delete(userExtRaw, "data") - userCopy.Ext, err = json.Marshal(userExtRaw) + requestData, err := adapter.makeRequest(request) if err != nil { - errs = append(errs, err) - return nil, errs + errors = append(errors, err) + continue } - request.User = &userCopy - } - // Setting ext client info - type bidRequestExt struct { - Client string `json:"client"` - } - request.Ext, err = json.Marshal(bidRequestExt{Client: clientVersion}) - if err != nil { - errs = append(errs, err) - return nil, errs + requests = append(requests, requestData) } + + return requests, errors +} + +func (adapter *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) if err != nil { - errs = append(errs, err) - return nil, errs + return nil, err } - uri := a.URI - headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ + return &adapters.RequestData{ Method: "POST", - Uri: uri, + Uri: adapter.endpoint, Body: reqJSON, Headers: headers, - }}, errs + }, nil } -// MakeBids unpacks the server's response into Bids. -func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{fmt.Errorf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} - } - - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} +func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) { + if admType := adMarkupType(response.Headers.Get("X-Smt-Adtype")); admType != "" { + return admType, nil + } else if strings.HasPrefix(adMarkup, `{"image":`) { + return smtAdTypeImg, nil + } else if strings.HasPrefix(adMarkup, `{"richmedia":`) { + return smtAdTypeRichmedia, nil + } else if strings.HasPrefix(adMarkup, ` 0 { + primaryCategory = bid.Cat[0] } + + var bidExt bidExt + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, &errortypes.BadServerResponse{Message: "Invalid bid.ext."} + } + + return &openrtb_ext.ExtBidPrebidVideo{ + Duration: bidExt.Duration, + PrimaryCategory: primaryCategory, + }, nil } diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go index c7c4a65017f..0012bd6158d 100644 --- a/adapters/smaato/smaato_test.go +++ b/adapters/smaato/smaato_test.go @@ -1,7 +1,12 @@ package smaato import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/stretchr/testify/assert" "testing" + "time" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -16,5 +21,93 @@ func TestJsonSamples(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } + adapter, _ := bidder.(*adapter) + assert.NotNil(t, adapter.clock) + adapter.clock = &mockTime{time: time.Date(2021, 6, 25, 10, 00, 0, 0, time.UTC)} + adapterstest.RunJSONBidderTest(t, "smaatotest", bidder) } + +func TestVideoWithCategoryAndDuration(t *testing.T) { + bidder := &adapter{} + + mockedReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "1_1", + Video: &openrtb2.Video{ + W: 640, + H: 360, + MIMEs: []string{"video/mp4"}, + MaxDuration: 60, + Protocols: []openrtb2.Protocol{2, 3, 5, 6}, + }, + Ext: json.RawMessage( + `{ + "bidder": { + "publisherId": "12345" + "adbreakId": "4123456" + } + }`, + )}, + }, + } + mockedExtReq := &adapters.RequestData{} + mockedBidResponse := &openrtb2.BidResponse{ + ID: "some-id", + SeatBid: []openrtb2.SeatBid{{ + Seat: "some-seat", + Bid: []openrtb2.Bid{{ + ID: "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + ImpID: "1_1", + Price: 0.01, + AdM: "", + Cat: []string{"IAB1"}, + Ext: json.RawMessage( + `{ + "duration": 5 + }`, + ), + }}, + }}, + } + body, _ := json.Marshal(mockedBidResponse) + mockedRes := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + + expectedBidCount := 1 + expectedBidType := openrtb_ext.BidTypeVideo + expectedBidDuration := 5 + expectedBidCategory := "IAB1" + expectedErrorCount := 0 + + bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes) + + if len(bidResponse.Bids) != expectedBidCount { + t.Errorf("should have 1 bid, bids=%v", bidResponse.Bids) + } + if bidResponse.Bids[0].BidType != expectedBidType { + t.Errorf("bid type should be video, bidType=%s", bidResponse.Bids[0].BidType) + } + if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration { + t.Errorf("video duration should be set") + } + if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory { + t.Errorf("bid category should be set") + } + if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory { + t.Errorf("bid category should be set") + } + if len(errors) != expectedErrorCount { + t.Errorf("should not have any errors, errors=%v", errors) + } +} + +type mockTime struct { + time time.Time +} + +func (mt *mockTime) Now() time.Time { + return mt.time +} diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json new file mode 100644 index 00000000000..e86fea8eb04 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json @@ -0,0 +1,354 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + }, + { + "id": "postbid_iframe", + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "ext": { + "bidder": { + "publisherId": "1100042526", + "adspaceId": "130563104" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "postbid_iframe", + "tagid": "130563104", + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042526" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6524", + "crid": "CR69382", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "postbid_iframe", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6524", + "crid": "CR69382", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "postbid_iframe", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json index 8194f568c28..2752fa6e6c7 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json @@ -160,7 +160,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -215,7 +215,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json index 46722c4ff71..bc3a3c28c87 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json @@ -164,7 +164,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -219,7 +219,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 1018dbc39ac..7f81d39cd81 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -129,7 +129,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -184,7 +184,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index 0ba4050a143..f83e347a684 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -180,7 +180,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json index bf939eb078a..317003f52b3 100644 --- a/adapters/smaato/smaatotest/exemplary/video-app.json +++ b/adapters/smaato/smaatotest/exemplary/video-app.json @@ -165,7 +165,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -220,7 +220,8 @@ "nurl": "https://nurl", "price": 0.01, "w": 1024, - "h": 768 + "h": 768, + "exp": 300 }, "type": "video" } diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index bad3825bb62..85699129180 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -122,7 +122,7 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -177,7 +177,8 @@ "nurl": "https://nurl", "price": 0.01, "w": 1024, - "h": 768 + "h": 768, + "exp": 300 }, "type": "video" } diff --git a/adapters/smaato/smaatotest/params/banner.json b/adapters/smaato/smaatotest/params/race/banner.json similarity index 100% rename from adapters/smaato/smaatotest/params/banner.json rename to adapters/smaato/smaatotest/params/race/banner.json diff --git a/adapters/smaato/smaatotest/params/race/video.json b/adapters/smaato/smaatotest/params/race/video.json new file mode 100644 index 00000000000..a84c44d4d8e --- /dev/null +++ b/adapters/smaato/smaatotest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "publisherId": "1100042525", + "adspaceId": "130563103" +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json new file mode 100644 index 00000000000..59302b6de59 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Adtype": ["Img"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index db724565d52..885c077e624 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -162,10 +162,9 @@ } } ], - "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json new file mode 100644 index 00000000000..cfb23bbef85 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Adtype": ["something"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unknown markup type something.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json new file mode 100644 index 00000000000..8e41524493d --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["something"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-ext-req.json deleted file mode 100644 index 0c970fc5bad..00000000000 --- a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "mockBidRequest": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } - }, - "imp": [ - { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - }, - { - "w": 320, - "h": 250 - } - ] - }, - "ext": { - } - } - ], - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 - }, - "user": { - "ext": { - "consent": "gdprConsentString" - } - }, - "regs": { - "coppa": 1, - "ext": { - "gdpr": 1, - "us_privacy": "uspConsentString" - } - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json deleted file mode 100644 index 768b4ef9d2c..00000000000 --- a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "mockBidRequest": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "imp": [ - { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "banner": { - "format": [] - }, - "ext": { - "bidder": { - "publisherId": "1100042525", - "adspaceId": "130563103" - } - } - } - ], - "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "headers": { - "Content-Type": ["application/json;charset=utf-8"], - "Accept": ["application/json"] - }, - "uri": "https://prebid/bidder", - "body": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "imp": [], - "site": { - "page": "prebid.org", - "publisher": { - "id": "1100042525" - } - }, - "ext": { - "client": "prebid_server_0.2" - } - } - } - } - ], - "expectedMakeRequestsErrors": [ - { - "value": "No sizes provided for Banner []", - "comparison": "literal" - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unexpected status code: 0. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json new file mode 100644 index 00000000000..0c3661bdf69 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No sizes provided for Banner.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json new file mode 100644 index 00000000000..2b59495c829 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "native": { + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid MediaType. Smaato only supports Banner and Video.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json new file mode 100644 index 00000000000..0df53e6c0bc --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "ext": "" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid site.ext.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-400.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json similarity index 97% rename from adapters/smaato/smaatotest/supplemental/status-code-400.json rename to adapters/smaato/smaatotest/supplemental/bad-status-code-response.json index fc84c93e269..a087594325e 100644 --- a/adapters/smaato/smaatotest/supplemental/status-code-400.json +++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -137,7 +137,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json similarity index 81% rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json rename to adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json index 7f05b2dff14..10f5ad474c6 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json @@ -2,10 +2,7 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" }, "imp": [ { @@ -37,8 +34,11 @@ "dnt": 0 }, "user": { - "gender": "M", - "ext": 99 + "ext": { + "data": { + "yob": "" + } + } }, "regs": { "coppa": 1, @@ -50,7 +50,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", + "value": "Invalid user.ext.data.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json new file mode 100644 index 00000000000..26df372e14f --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "user": { + "ext": 99 + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid user.ext.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json new file mode 100644 index 00000000000..9e57c380d27 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json @@ -0,0 +1,173 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320 + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 320, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 320, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json new file mode 100644 index 00000000000..90a78e626cf --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["1624618800000"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 3600 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json similarity index 65% rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json rename to adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json index 9e65fce1c3e..88b0a4080e6 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json +++ b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json @@ -2,9 +2,18 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", "publisher": { - "id": "1" + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } } }, "imp": [ @@ -24,8 +33,7 @@ }, "ext": { "bidder": { - "publisherId": "1100042525", - "adspaceId": "130563103" + "publisherId": "1100042525" } } } @@ -37,17 +45,16 @@ "dnt": 0 }, "user": { - "gender": "M", "ext": { + "consent": "gdprConsentString", "data": { - "keywords":"a,b", + "keywords": "a,b", "gender": "M", - "yob": "", + "yob": 1984, "geo": { "country": "ca" } - }, - "consent":"yes" + } } }, "regs": { @@ -60,7 +67,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field userExtData.data.yob of type int64", + "value": "Missing adspaceId parameter.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/no-app-site-request.json b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json new file mode 100644 index 00000000000..04a73b4f40d --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing Site/App.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-204.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json similarity index 96% rename from adapters/smaato/smaatotest/supplemental/status-code-204.json rename to adapters/smaato/smaatotest/supplemental/no-bid-response.json index b409597f986..c8821d2d944 100644 --- a/adapters/smaato/smaatotest/supplemental/status-code-204.json +++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -133,7 +133,5 @@ "status": 204 } } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [] + ] } \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json similarity index 98% rename from adapters/smaato/smaatotest/supplemental/no-consent-info.json rename to adapters/smaato/smaatotest/supplemental/no-consent-info-request.json index b9a4294b00b..72f8a2e3b9d 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -127,7 +127,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/supplemental/no-imp-req.json b/adapters/smaato/smaatotest/supplemental/no-imp-request.json similarity index 59% rename from adapters/smaato/smaatotest/supplemental/no-imp-req.json rename to adapters/smaato/smaatotest/supplemental/no-imp-request.json index bfaf51e6ea8..eab4f2a0697 100644 --- a/adapters/smaato/smaatotest/supplemental/no-imp-req.json +++ b/adapters/smaato/smaatotest/supplemental/no-imp-request.json @@ -2,15 +2,12 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } + "page": "prebid.org" } }, "expectedMakeRequestsErrors": [ { - "value": "no impressions in bid request", + "value": "No impressions in bid request.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json new file mode 100644 index 00000000000..60a0af594a8 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing publisherId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json new file mode 100644 index 00000000000..c65e0824338 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json @@ -0,0 +1,193 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["1524618800000"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json new file mode 100644 index 00000000000..c909dc15f25 --- /dev/null +++ b/adapters/smaato/smaatotest/video/multiple-adpods.json @@ -0,0 +1,555 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "2_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "2_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "2_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "2_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json new file mode 100644 index 00000000000..b5bc09d495c --- /dev/null +++ b/adapters/smaato/smaatotest/video/single-adpod.json @@ -0,0 +1,293 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json new file mode 100644 index 00000000000..308648d3f64 --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1234567" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1234567" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing adbreakId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json new file mode 100644 index 00000000000..b13906ce066 --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json @@ -0,0 +1,276 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid ad markup .", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json new file mode 100644 index 00000000000..2e0556ff15e --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json @@ -0,0 +1,274 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.3" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": "" + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid bid.ext.", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json new file mode 100644 index 00000000000..1c345de029d --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1_1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid MediaType. Smaato only supports Video for AdPod.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json new file mode 100644 index 00000000000..1615b670e9b --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing publisherId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/openrtb_ext/imp_smaato.go b/openrtb_ext/imp_smaato.go index 10de97fb017..14dcb73bdf3 100644 --- a/openrtb_ext/imp_smaato.go +++ b/openrtb_ext/imp_smaato.go @@ -1,9 +1,12 @@ package openrtb_ext // ExtImpSmaato defines the contract for bidrequest.imp[i].ext.smaato -// PublisherId and AdSpaceId are mandatory parameters, others are optional parameters +// PublisherId and AdSpaceId are mandatory parameters for non adpod (long-form video) requests, others are optional parameters +// PublisherId and AdBreakId are mandatory parameters for adpod (long-form video) requests, others are optional parameters // AdSpaceId is identifier for specific ad placement or ad tag +// AdBreakId is identifier for specific ad placement or ad tag type ExtImpSmaato struct { PublisherID string `json:"publisherId"` AdSpaceID string `json:"adspaceId"` + AdBreakID string `json:"adbreakId"` } diff --git a/static/bidder-params/smaato.json b/static/bidder-params/smaato.json index aa91c4bacc5..e4584b86860 100644 --- a/static/bidder-params/smaato.json +++ b/static/bidder-params/smaato.json @@ -11,7 +11,25 @@ "adspaceId": { "type": "string", "description": "Identifier for specific ad placement is SOMA `adspaceId`" + }, + "adbreakId": { + "type": "string", + "description": "Identifier for specific adpod placement is SOMA `adbreakId`" } }, - "required": ["publisherId","adspaceId"] + "required": [ + "publisherId" + ], + "anyOf": [ + { + "required": [ + "adspaceId" + ] + }, + { + "required": [ + "adbreakId" + ] + } + ] } \ No newline at end of file From d58e15fdff451074b6eb809c5a52c53333a0b68c Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 15 Jul 2021 04:55:35 -0700 Subject: [PATCH 459/603] Allowed $0.00 price bids if there are deals (#1910) --- exchange/bidder_validate_bids.go | 7 +++- exchange/bidder_validate_bids_test.go | 60 +++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index aec0948ddde..d58c9ebba50 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -110,8 +110,11 @@ func validateBid(bid *pbsOrtbBid) (bool, error) { if bid.bid.ImpID == "" { return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.bid.ID) } - if bid.bid.Price <= 0.0 { - return false, fmt.Errorf("Bid \"%s\" does not contain a positive 'price'", bid.bid.ID) + if bid.bid.Price < 0.0 { + return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.bid.ID) + } + if bid.bid.Price == 0.0 && bid.bid.DealID == "" { + return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.bid.ID) } if bid.bid.CrID == "" { return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.bid.ID) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 06973b837c2..37c7bbec1eb 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -39,11 +39,20 @@ func TestAllValidBids(t *testing.T) { CrID: "789", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBid", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "777", + }, + }, }, }, }) seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, 3) + assert.Len(t, seatBid.bids, 4) assert.Len(t, errs, 0) } @@ -79,13 +88,30 @@ func TestAllBadBids(t *testing.T) { CrID: "blah", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBidNoDeal", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "negativePrice", + ImpID: "999", + Price: -0.10, + CrID: "888", + }, + }, {}, }, }, }) seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 0) - assert.Len(t, errs, 5) + assert.Len(t, errs, 7) } func TestMixedBids(t *testing.T) { @@ -122,13 +148,39 @@ func TestMixedBids(t *testing.T) { CrID: "blah", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBid", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "777", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBidNoDeal", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "negativePrice", + ImpID: "999", + Price: -0.10, + CrID: "888", + }, + }, {}, }, }, }) seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) - assert.Len(t, seatBid.bids, 2) - assert.Len(t, errs, 3) + assert.Len(t, seatBid.bids, 3) + assert.Len(t, errs, 5) } func TestCurrencyBids(t *testing.T) { From 09291bbd3736ed7d3e289b2fc8aeb753280f0a7a Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 15 Jul 2021 14:26:08 -0400 Subject: [PATCH 460/603] GDPR: host-level per-purpose enforce vendor signals config (#1921) * Add GDPR host-level per-purpose enforce vendor signals config * Update config defaults test with TCF2 object compare --- config/config.go | 13 +- config/config_test.go | 97 +++++++++++- gdpr/impl.go | 36 ++++- gdpr/impl_test.go | 349 +++++++++++++++++++++++++----------------- 4 files changed, 353 insertions(+), 142 deletions(-) diff --git a/config/config.go b/config/config.go index 96e3c1f1e65..b64bab0006f 100644 --- a/config/config.go +++ b/config/config.go @@ -257,7 +257,8 @@ type TCF2 struct { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` + Enabled bool `mapstructure:"enabled"` + EnforceVendors bool `mapstructure:"enforce_vendors"` // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` VendorExceptionMap map[openrtb_ext.BidderName]struct{} @@ -999,6 +1000,16 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.tcf2.purpose8.enabled", true) v.SetDefault("gdpr.tcf2.purpose9.enabled", true) v.SetDefault("gdpr.tcf2.purpose10.enabled", true) + v.SetDefault("gdpr.tcf2.purpose1.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose2.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose3.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose4.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose5.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose6.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose7.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose8.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose9.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose10.enforce_vendors", true) v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []openrtb_ext.BidderName{}) diff --git a/config/config_test.go b/config/config_test.go index c33a345c473..a87d65af359 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -138,8 +138,81 @@ 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) - cmpBools(t, "gdpr.tcf2.purpose_one_treatment.enabled", true, cfg.GDPR.TCF2.PurposeOneTreatment.Enabled) - cmpBools(t, "gdpr.tcf2.purpose_one_treatment.access_allowed", true, cfg.GDPR.TCF2.PurposeOneTreatment.AccessAllowed) + + //Assert purpose VendorExceptionMap hash tables were built correctly + expectedTCF2 := TCF2{ + Enabled: true, + Purpose1: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose2: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose3: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose4: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose5: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose6: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose7: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose8: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose9: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose10: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + SpecialPurpose1: TCF2Purpose{ + Enabled: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + PurposeOneTreatment: TCF2PurposeOneTreatment{ + Enabled: true, + AccessAllowed: true, + }, + } + assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") } var fullConfig = []byte(` @@ -149,25 +222,35 @@ gdpr: non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] tcf2: purpose1: + enforce_vendors: false vendor_exceptions: ["foo1a", "foo1b"] purpose2: enabled: false + enforce_vendors: false vendor_exceptions: ["foo2"] purpose3: + enforce_vendors: false vendor_exceptions: ["foo3"] purpose4: + enforce_vendors: false vendor_exceptions: ["foo4"] purpose5: + enforce_vendors: false vendor_exceptions: ["foo5"] purpose6: + enforce_vendors: false vendor_exceptions: ["foo6"] purpose7: + enforce_vendors: false vendor_exceptions: ["foo7"] purpose8: + enforce_vendors: false vendor_exceptions: ["foo8"] purpose9: + enforce_vendors: false vendor_exceptions: ["foo9"] purpose10: + enforce_vendors: false vendor_exceptions: ["foo10"] special_purpose1: vendor_exceptions: ["fooSP1"] @@ -407,51 +490,61 @@ func TestFullConfig(t *testing.T) { Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, }, Purpose2: TCF2Purpose{ Enabled: false, + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, }, Purpose3: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, }, Purpose4: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, }, Purpose5: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, }, Purpose6: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, }, Purpose7: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, }, Purpose8: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, }, Purpose9: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, }, Purpose10: TCF2Purpose{ Enabled: true, // true by default + EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, }, diff --git a/gdpr/impl.go b/gdpr/impl.go index ac0bfefdba4..5f7e3e73fe2 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -210,8 +210,8 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return true } - purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) - legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) + purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement) + legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement) if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { return purposeAllowed @@ -224,6 +224,38 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return purposeAllowed || legitInterest } +func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if !consent.PurposeAllowed(purpose) { + return false + } + if weakVendorEnforcement { + return true + } + if !p.purposeConfigs[purpose].EnforceVendors { + return true + } + if vendor.Purpose(purpose) && consent.VendorConsent(vendorID) { + return true + } + return false +} + +func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if !consent.PurposeLITransparency(purpose) { + return false + } + if weakVendorEnforcement { + return true + } + if !p.purposeConfigs[purpose].EnforceVendors { + return true + } + if vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID) { + return true + } + return false +} + func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) { parsedConsent, err = vendorconsent.ParseString(consent) if err != nil { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 93d23c1acf6..f7d90f3673b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -66,7 +66,8 @@ func TestAllowedSyncs(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -79,6 +80,9 @@ func TestAllowedSyncs(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) @@ -145,7 +149,8 @@ func TestProhibitedVendors(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -158,6 +163,9 @@ func TestProhibitedVendors(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) @@ -289,7 +297,8 @@ func TestAllowActivities(t *testing.T) { TCF2: config.TCF2{ Enabled: true, Purpose2: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -302,6 +311,9 @@ func TestAllowActivities(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + } for _, tt := range tests { perms.cfg.DefaultValue = tt.gdprDefaultValue @@ -357,15 +369,39 @@ func buildVendorList34() vendorList { } } -var gdprConfig = config.GDPR{ - HostVendorID: 2, - TCF2: config.TCF2{ - Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true}, - Purpose2: config.TCF2Purpose{Enabled: true}, - Purpose7: config.TCF2Purpose{Enabled: true}, - SpecialPurpose1: config.TCF2Purpose{Enabled: true}, - }, +func allPurposesEnabledPermissions() (perms permissionsImpl) { + perms = permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose2: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose3: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose4: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose5: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose6: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose7: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose8: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose9: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose10: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + }, + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3, + consentconstants.Purpose(4): perms.cfg.TCF2.Purpose4, + consentconstants.Purpose(5): perms.cfg.TCF2.Purpose5, + consentconstants.Purpose(6): perms.cfg.TCF2.Purpose6, + consentconstants.Purpose(7): perms.cfg.TCF2.Purpose7, + consentconstants.Purpose(8): perms.cfg.TCF2.Purpose8, + consentconstants.Purpose(9): perms.cfg.TCF2.Purpose9, + consentconstants.Purpose(10): perms.cfg.TCF2.Purpose10, + } + return } type testDef struct { @@ -380,20 +416,20 @@ type testDef struct { func TestAllowActivitiesGeoAndID(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - 74: parseVendorListDataV2(t, vendorListData), - }), - }, + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAudienceNetwork: 55, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + 74: parseVendorListDataV2(t, vendorListData), + }), } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 @@ -464,19 +500,19 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { func TestAllowActivitiesWhitelist(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } + // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}} _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) @@ -487,18 +523,17 @@ func TestAllowActivitiesWhitelist(t *testing.T) { func TestAllowActivitiesPubRestrict(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 32, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 15: parseVendorListDataV2(t, vendorListData), - }), - }, + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 32, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 15: parseVendorListDataV2(t, vendorListData), + }), } // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, @@ -537,18 +572,17 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { func TestAllowSync(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 @@ -565,20 +599,19 @@ func TestProhibitedPurposeSync(t *testing.T) { vendorList34 := buildVendorList34() vendorList34.Vendors["8"].Purposes = []int{7} vendorListData := MarshalVendorList(vendorList34) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } + + perms := allPurposesEnabledPermissions() perms.cfg.HostVendorID = 8 + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") @@ -592,21 +625,20 @@ func TestProhibitedPurposeSync(t *testing.T) { func TestProhibitedVendorSync(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 10, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } + + perms := allPurposesEnabledPermissions() perms.cfg.HostVendorID = 10 + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 10, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") @@ -751,67 +783,110 @@ func TestAllowActivitiesBidRequests(t *testing.T) { purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" + purpose2AndVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAAAAAIAIAAA" + purpose2LIWithoutVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAABIAAAAA" + testDefs := []struct { - description string - purpose2Enabled bool - bidder openrtb_ext.BidderName - consent string - allowBid bool - passGeo bool - passID bool - weakVendorEnforcement bool + description string + purpose2Enabled bool + purpose2EnforceVendors bool + bidder openrtb_ext.BidderName + consent string + allowBid bool + passGeo bool + passID bool + weakVendorEnforcement bool }{ { - description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: true, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2ConsentWithoutVendorConsent, - allowBid: false, - passGeo: false, - passID: false, + description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: false, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: false, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: true, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorConsent, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderRubicon, + consent: purpose2LIWithoutVendorLI, + allowBid: false, + passGeo: false, + passID: false, }, { - description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: false, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2ConsentWithoutVendorConsent, - allowBid: true, - passGeo: false, - passID: false, + description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderRubicon, + consent: purpose2AndVendorLI, + allowBid: true, + passGeo: false, + passID: true, }, { - description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", - purpose2Enabled: true, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2AndVendorConsent, - allowBid: true, - passGeo: false, - passID: true, + description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 LI but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: false, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorLI, + allowBid: true, + passGeo: false, + passID: true, }, } for _, td := range testDefs { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: config.GDPR{ - HostVendorID: 2, - TCF2: config.TCF2{ - Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true}, - Purpose2: config.TCF2Purpose{Enabled: td.purpose2Enabled}, - Purpose7: config.TCF2Purpose{Enabled: true}, - SpecialPurpose1: config.TCF2Purpose{Enabled: true}, - }, - }, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderPubmatic: 6, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), } + perms.cfg.TCF2.Purpose2.Enabled = td.purpose2Enabled + p2Config := perms.purposeConfigs[consentconstants.Purpose(2)] + p2Config.Enabled = td.purpose2Enabled + p2Config.EnforceVendors = td.purpose2EnforceVendors + perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) From aee52e33a5b531659728a474f81121da40e276cd Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 15 Jul 2021 11:43:57 -0700 Subject: [PATCH 461/603] Fix for fetcher warning at server startup (#1914) Co-authored-by: Veronika Solovei --- stored_requests/config/config.go | 3 ++ stored_requests/config/config_test.go | 78 +++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 7f92f2521cd..f682ff932f4 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -166,6 +166,9 @@ func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fe if cfg.Postgres.FetcherQueries.QueryTemplate != "" { glog.Infof("Loading Stored %s data via Postgres.\nQuery: %s", cfg.DataType(), cfg.Postgres.FetcherQueries.QueryTemplate) idList = append(idList, db_fetcher.NewFetcher(db, cfg.Postgres.FetcherQueries.MakeQuery)) + } else if cfg.Postgres.CacheInitialization.Query != "" && cfg.Postgres.PollUpdates.Query != "" { + //in this case data will be loaded to cache via poll for updates event + idList = append(idList, empty_fetcher.EmptyFetcher{}) } if cfg.HTTP.Endpoint != "" { glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint) diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index 6c8cd612299..4a8d10a9382 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -2,6 +2,7 @@ package config import ( "context" + "database/sql" "encoding/json" "errors" "net/http" @@ -41,12 +42,79 @@ func isMemoryCacheType(cache stored_requests.CacheJSON) bool { } func TestNewEmptyFetcher(t *testing.T) { - fetcher := newFetcher(&config.StoredRequests{}, nil, nil) - if fetcher == nil { - t.Errorf("The fetcher should be non-nil, even with an empty config.") + + type testCase struct { + config *config.StoredRequests + emptyFetcher bool + description string } - if _, ok := fetcher.(empty_fetcher.EmptyFetcher); !ok { - t.Errorf("If the config is empty, and EmptyFetcher should be returned") + testCases := []testCase{ + { + config: &config.StoredRequests{}, + emptyFetcher: true, + description: "If the config is empty, an EmptyFetcher should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "test query", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "test poll query", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "", + }, + }, + }, + emptyFetcher: true, + description: "If Postgres fetcher query is not defined, but Postgres Cache init query and Postgres update polling query are defined EmptyFetcher should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "test fetcher query", + }, + }, + }, + emptyFetcher: false, + description: "If Postgres fetcher query is defined, but Postgres Cache init query and Postgres update polling query are not defined not EmptyFetcher (DBFetcher) should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "test cache query", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "test poll query", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "test fetcher query", + }, + }, + }, + emptyFetcher: false, + description: "If Postgres fetcher query is defined and Postgres Cache init query and Postgres update polling query are defined not EmptyFetcher (DBFetcher) should be returned", + }, + } + + for _, test := range testCases { + fetcher := newFetcher(test.config, nil, &sql.DB{}) + assert.NotNil(t, fetcher, "The fetcher should be non-nil.") + if test.emptyFetcher { + assert.Equal(t, empty_fetcher.EmptyFetcher{}, fetcher, "Empty fetcher should be returned") + } else { + assert.NotEqual(t, empty_fetcher.EmptyFetcher{}, fetcher) + } } } From ac3c657339a0cfbf0beecadb0f68cb571f8d9d00 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:14:55 -0400 Subject: [PATCH 462/603] Request Wrapper first pass (#1784) --- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/amp_auction_test.go | 1 + endpoints/openrtb2/auction.go | 242 +++--- endpoints/openrtb2/auction_test.go | 21 +- endpoints/openrtb2/interstitial.go | 18 +- endpoints/openrtb2/interstitial_test.go | 3 +- .../invalid-whole/invalid-source.json | 2 +- .../invalid-whole/regs-ext-gdpr-string.json | 2 +- .../invalid-whole/regs-ext-malformed.json | 2 +- .../invalid-whole/user-ext-consent-int.json | 2 +- .../user-gdpr-consent-invalid.json | 2 +- endpoints/openrtb2/video_auction.go | 2 +- endpoints/openrtb2/video_auction_test.go | 4 +- exchange/utils.go | 30 +- exchange/utils_test.go | 8 +- go.sum | 2 + openrtb_ext/device.go | 8 +- openrtb_ext/request_wrapper.go | 787 ++++++++++++++++++ openrtb_ext/request_wrapper_test.go | 22 + privacy/ccpa/consentwriter.go | 19 +- privacy/ccpa/consentwriter_test.go | 50 ++ privacy/ccpa/policy.go | 184 +--- privacy/ccpa/policy_test.go | 123 ++- 23 files changed, 1185 insertions(+), 351 deletions(-) create mode 100644 openrtb_ext/request_wrapper.go create mode 100644 openrtb_ext/request_wrapper_test.go diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 862311daa6e..6b4fa6890b8 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -310,7 +310,7 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr // At this point, we should have a valid request that definitely has Targeting and Cache turned on - e = deps.validateRequest(req) + e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}) errs = append(errs, e...) return } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 61164bd7272..2ce1180d50b 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -66,6 +66,7 @@ func TestGoodAmpRequests(t *testing.T) { var response AmpResponse if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + t.Errorf("AMP response was: %s", recorder.Body.Bytes()) t.Fatalf("Error unmarshalling response: %s", err.Error()) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index c9f2bbdb68f..a6b3479a36c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -174,10 +174,17 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http return } + // rebuild/resync the request in the request wrapper. + if err := req.RebuildRequest(); err != nil { + errL = append(errL, err) + writeError(errL, w, &labels) + return + } + secGPC := r.Header.Get("Sec-GPC") auctionRequest := exchange.AuctionRequest{ - BidRequest: req, + BidRequest: req.BidRequest, Account: *account, UserSyncs: usersyncs, RequestType: labels.RType, @@ -188,7 +195,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) - ao.Request = req + ao.Request = req.BidRequest ao.Response = response ao.Account = account if err != nil { @@ -227,8 +234,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http // possible, it will return errors with messages that suggest improvements. // // If the errors list has at least one element, then no guarantees are made about the returned request. -func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { - req = &openrtb2.BidRequest{} +func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, errs []error) { + req = &openrtb_ext.RequestWrapper{} + req.BidRequest = &openrtb2.BidRequest{} errs = nil // Pull the request body into a buffer, so we have it for later usage. @@ -258,20 +266,20 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2 return } - if err := json.Unmarshal(requestJson, req); err != nil { + if err := json.Unmarshal(requestJson, req.BidRequest); err != nil { errs = []error{err} return } // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(httpRequest, req) + deps.setFieldsImplicitly(httpRequest, req.BidRequest) if err := processInterstitials(req); err != nil { errs = []error{err} return } - lmt.ModifyForIOS(req) + lmt.ModifyForIOS(req.BidRequest) errL := deps.validateRequest(req) if len(errL) > 0 { @@ -296,7 +304,7 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } -func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { +func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -318,34 +326,39 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { // If automatically filling source TID is enabled then validate that // source.TID exists and If it doesn't, fill it with a randomly generated UUID if deps.cfg.AutoGenSourceTID { - if err := validateAndFillSourceTID(req); err != nil { + if err := validateAndFillSourceTID(req.BidRequest); err != nil { return []error{err} } } var aliases map[string]string - if bidExt, err := deps.parseBidExt(req.Ext); err != nil { + reqExt, err := req.GetRequestExt() + if err != nil { + return []error{fmt.Errorf("request.ext is invalid: %v", err)} + } + reqPrebid := reqExt.GetPrebid() + if err := deps.parseBidExt(req); err != nil { return []error{err} - } else if bidExt != nil { - aliases = bidExt.Prebid.Aliases + } else if reqPrebid != nil { + aliases = reqPrebid.Aliases if err := deps.validateAliases(aliases); err != nil { return []error{err} } - if err := deps.validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil { + if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil { return []error{err} } - if err := validateSChains(bidExt); err != nil { + if err := validateSChains(reqPrebid.SChains); err != nil { return []error{err} } - if err := deps.validateEidPermissions(bidExt, aliases); err != nil { + if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { return []error{err} } - if err := validateCustomRates(bidExt.Prebid.CurrencyConversions); err != nil { + if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil { return []error{err} } } @@ -354,19 +367,19 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { return append(errL, errors.New("request.site or request.app must be defined, but not both.")) } - if err := deps.validateSite(req.Site); err != nil { + if err := deps.validateSite(req); err != nil { return append(errL, err) } - if err := deps.validateApp(req.App); err != nil { + if err := deps.validateApp(req); err != nil { return append(errL, err) } - if err := deps.validateUser(req.User, aliases); err != nil { + if err := deps.validateUser(req, aliases); err != nil { return append(errL, err) } - if err := validateRegs(req.Regs); err != nil { + if err := validateRegs(req); err != nil { return append(errL, err) } @@ -374,17 +387,18 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { return append(errL, err) } - if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { + if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { errL = append(errL, &errortypes.Warning{ Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) - consentWriter := ccpa.ConsentWriter{Consent: ""} - if err := consentWriter.Write(req); err != nil { - return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) + regsExt, err := req.GetRegExt() + if err != nil { + return append(errL, err) } + regsExt.SetUSPrivacy("") } else { return append(errL, err) } @@ -437,8 +451,8 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str return nil } -func validateSChains(req *openrtb_ext.ExtRequest) error { - _, err := exchange.BidderToPrebidSChains(req) +func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { + _, err := exchange.BidderToPrebidSChains(sChains) return err } @@ -466,13 +480,13 @@ func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) er return nil } -func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error { - if req == nil || req.Prebid.Data == nil { +func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { + if prebid == nil { return nil } - uniqueSources := make(map[string]struct{}, len(req.Prebid.Data.EidPermissions)) - for i, eid := range req.Prebid.Data.EidPermissions { + uniqueSources := make(map[string]struct{}, len(prebid.EidPermissions)) + for i, eid := range prebid.EidPermissions { if len(eid.Source) == 0 { return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing required field: "source"`, i) } @@ -1045,15 +1059,11 @@ func isBidderToValidate(bidder string) bool { } } -func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { - if len(ext) < 1 { - return nil, nil +func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { + if _, err := req.GetRequestExt(); err != nil { + return fmt.Errorf("request.ext is invalid: %v", err) } - var tmpExt openrtb_ext.ExtRequest - if err := json.Unmarshal(ext, &tmpExt); err != nil { - return nil, fmt.Errorf("request.ext is invalid: %v", err) - } - return &tmpExt, nil + return nil } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { @@ -1073,120 +1083,112 @@ func (deps *endpointDeps) validateAliases(aliases map[string]string) error { return nil } -func (deps *endpointDeps) validateSite(site *openrtb2.Site) error { - if site == nil { +func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error { + if req.Site == nil { return nil } - if site.ID == "" && site.Page == "" { + if req.Site.ID == "" && req.Site.Page == "" { return errors.New("request.site should include at least one of request.site.id or request.site.page.") } - if len(site.Ext) > 0 { - var s openrtb_ext.ExtSite - if err := json.Unmarshal(site.Ext, &s); err != nil { - return err - } + siteExt, err := req.GetSiteExt() + if err != nil { + return err + } + siteAmp := siteExt.GetAmp() + if siteAmp < 0 || siteAmp > 1 { + return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } return nil } -func (deps *endpointDeps) validateApp(app *openrtb2.App) error { - if app == nil { +func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { + if req.App == nil { return nil } - if app.ID != "" { - if _, found := deps.cfg.BlacklistedAppMap[app.ID]; found { - return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", app.ID)} - } - } - - if len(app.Ext) > 0 { - var a openrtb_ext.ExtApp - if err := json.Unmarshal(app.Ext, &a); err != nil { - return err + if req.App.ID != "" { + if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { + return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} } } - return nil + _, err := req.GetAppExt() + return err } -func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]string) error { - if user == nil { - return nil - } - +func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string) error { // The following fields were previously uints in the OpenRTB library we use, but have // since been changed to ints. We decided to maintain the non-negative check. - if user.Geo != nil && user.Geo.Accuracy < 0 { - return errors.New("request.user.geo.accuracy must be a positive number") - } - - if user.Ext != nil { - // Creating ExtUser object - var userExt openrtb_ext.ExtUser - if err := json.Unmarshal(user.Ext, &userExt); err == nil { - // Check if the buyeruids are valid - if userExt.Prebid != nil { - if len(userExt.Prebid.BuyerUIDs) < 1 { - return errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`) - } - for bidderName := range userExt.Prebid.BuyerUIDs { - if _, ok := deps.bidderMap[bidderName]; !ok { - if _, ok := aliases[bidderName]; !ok { - return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName) - } - } + if req != nil && req.BidRequest != nil && req.User != nil { + if req.User.Geo != nil && req.User.Geo.Accuracy < 0 { + return errors.New("request.user.geo.accuracy must be a positive number") + } + } + + userExt, err := req.GetUserExt() + if err != nil { + return fmt.Errorf("request.user.ext object is not valid: %v", err) + } + // Check if the buyeruids are valid + prebid := userExt.GetPrebid() + if prebid != nil { + if len(prebid.BuyerUIDs) < 1 { + return errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`) + } + for bidderName := range prebid.BuyerUIDs { + if _, ok := deps.bidderMap[bidderName]; !ok { + if _, ok := aliases[bidderName]; !ok { + return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName) } } - // Check Universal User ID - if userExt.Eids != nil { - if len(userExt.Eids) == 0 { - return fmt.Errorf("request.user.ext.eids must contain at least one element or be undefined") - } - uniqueSources := make(map[string]struct{}, len(userExt.Eids)) - for eidIndex, eid := range userExt.Eids { - if eid.Source == "" { - return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex) - } - if _, ok := uniqueSources[eid.Source]; ok { - return fmt.Errorf("request.user.ext.eids must contain unique sources") - } - uniqueSources[eid.Source] = struct{}{} + } + } + // Check Universal User ID + eids := userExt.GetEid() + if eids != nil { + if len(*eids) == 0 { + return errors.New("request.user.ext.eids must contain at least one element or be undefined") + } + uniqueSources := make(map[string]struct{}, len(*eids)) + for eidIndex, eid := range *eids { + if eid.Source == "" { + return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex) + } + if _, ok := uniqueSources[eid.Source]; ok { + return errors.New("request.user.ext.eids must contain unique sources") + } + uniqueSources[eid.Source] = struct{}{} - if eid.ID == "" && eid.Uids == nil { - return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex) - } - if eid.ID == "" { - if len(eid.Uids) == 0 { - return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex) - } - for uidIndex, uid := range eid.Uids { - if uid.ID == "" { - return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex) - } - } + if eid.ID == "" && eid.Uids == nil { + return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex) + } + if eid.ID == "" { + if len(eid.Uids) == 0 { + return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex) + } + for uidIndex, uid := range eid.Uids { + if uid.ID == "" { + return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex) } } } - } else { - return fmt.Errorf("request.user.ext object is not valid: %v", err) } } return nil } -func validateRegs(regs *openrtb2.Regs) error { - if regs != nil && len(regs.Ext) > 0 { - var regsExt openrtb_ext.ExtRegs - if err := json.Unmarshal(regs.Ext, ®sExt); err != nil { - return fmt.Errorf("request.regs.ext is invalid: %v", err) - } - if regsExt.GDPR != nil && (*regsExt.GDPR < 0 || *regsExt.GDPR > 1) { - return errors.New("request.regs.ext.gdpr must be either 0 or 1.") - } +func validateRegs(req *openrtb_ext.RequestWrapper) error { + regsExt, err := req.GetRegExt() + if err != nil { + return fmt.Errorf("request.regs.ext is invalid: %v", err) + } + regExt := regsExt.GetExt() + gdprJSON, hasGDPR := regExt["gdpr"] + if hasGDPR && (string(gdprJSON) != "0" && string(gdprJSON) != "1") { + return errors.New("request.regs.ext.gdpr must be either 0 or 1.") } return nil } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index bcdac13dc06..4835dd92943 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1587,7 +1587,7 @@ func TestCurrencyTrunc(t *testing.T) { Cur: []string{"USD", "EUR"}, } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"} assert.ElementsMatch(t, errL, []error{&expectedError}) @@ -1633,14 +1633,12 @@ func TestCCPAInvalid(t *testing.T) { }, } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedWarning := errortypes.Warning{ Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", WarningCode: errortypes.InvalidPrivacyConsentWarningCode} assert.ElementsMatch(t, errL, []error{&expectedWarning}) - - assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } func TestNoSaleInvalid(t *testing.T) { @@ -1684,7 +1682,7 @@ func TestNoSaleInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1731,7 +1729,7 @@ func TestValidateSourceTID(t *testing.T) { }, } - deps.validateRequest(&req) + deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") } @@ -1773,7 +1771,7 @@ func TestSChainInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1992,7 +1990,7 @@ func TestEidPermissionsInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`) assert.ElementsMatch(t, errL, []error{expectedError}) @@ -2007,11 +2005,6 @@ func TestValidateEidPermissions(t *testing.T) { request *openrtb_ext.ExtRequest expectedError error }{ - { - description: "Valid - Nil ext", - request: nil, - expectedError: nil, - }, { description: "Valid - Empty ext", request: &openrtb_ext.ExtRequest{}, @@ -2096,7 +2089,7 @@ func TestValidateEidPermissions(t *testing.T) { endpoint := &endpointDeps{bidderMap: knownBidders} for _, test := range testCases { - result := endpoint.validateEidPermissions(test.request, knownAliases) + result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) assert.Equal(t, test.expectedError, result, test.description) } } diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 1aa2a7fc890..359bae11d4c 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -1,7 +1,6 @@ package openrtb2 import ( - "encoding/json" "fmt" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -10,26 +9,27 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func processInterstitials(req *openrtb2.BidRequest) error { - var devExt openrtb_ext.ExtDevice +func processInterstitials(req *openrtb_ext.RequestWrapper) error { unmarshalled := true for i := range req.Imp { if req.Imp[i].Instl == 1 { + var prebid *openrtb_ext.ExtDevicePrebid if unmarshalled { if req.Device.Ext == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } - err := json.Unmarshal(req.Device.Ext, &devExt) + deviceExt, err := req.GetDeviceExt() if err != nil { return err } - if devExt.Prebid.Interstitial == nil { + prebid = deviceExt.GetPrebid() + if prebid.Interstitial == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } } - err := processInterstitialsForImp(&req.Imp[i], &devExt, req.Device) + err := processInterstitialsForImp(&req.Imp[i], prebid, req.Device) if err != nil { return err } @@ -38,7 +38,7 @@ func processInterstitials(req *openrtb2.BidRequest) error { return nil } -func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb2.Device) error { +func processInterstitialsForImp(imp *openrtb2.Imp, devExtPrebid *openrtb_ext.ExtDevicePrebid, device *openrtb2.Device) error { var maxWidth, maxHeight, minWidth, minHeight int64 if imp.Banner == nil { // custom interstitial support is only available for banner requests. @@ -56,8 +56,8 @@ func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice maxWidth = device.W maxHeight = device.H } - minWidth = (maxWidth * int64(devExt.Prebid.Interstitial.MinWidthPerc)) / 100 - minHeight = (maxHeight * int64(devExt.Prebid.Interstitial.MinHeightPerc)) / 100 + minWidth = (maxWidth * devExtPrebid.Interstitial.MinWidthPerc) / 100 + minHeight = (maxHeight * devExtPrebid.Interstitial.MinHeightPerc) / 100 imp.Banner.Format = genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight) if len(imp.Banner.Format) == 0 { return &errortypes.BadInput{Message: fmt.Sprintf("Unable to set interstitial size list for Imp id=%s (No valid sizes between %dx%d and %dx%d)", imp.ID, minWidth, minHeight, maxWidth, maxHeight)} diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 1d7ad9e3d6b..fe0ed966c3c 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -34,7 +35,7 @@ var request = &openrtb2.BidRequest{ func TestInterstitial(t *testing.T) { myRequest := request - if err := processInterstitials(myRequest); err != nil { + if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil { t.Fatalf("Error processing interstitials: %v", err) } targetFormat := []openrtb2.Format{ diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json index 8385f924a56..5aa7fd4dea1 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json @@ -39,5 +39,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.prebid.source of type string" + "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.source of type string" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json index afdabdab7cf..4a315911906 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json @@ -44,5 +44,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go struct field ExtRegs.gdpr of type int8\n" + "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json index a8e94008cf1..ab44e3e2428 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json @@ -42,5 +42,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type openrtb_ext.ExtRegs\n" + "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type map[string]json.RawMessage\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json index b61be105df0..a26db8a5695 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json @@ -46,5 +46,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go struct field ExtUser.consent of type string\n" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json index 08eed44b2b0..c4646550dd2 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json @@ -41,5 +41,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go struct field ExtUser.consent of type string" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string" } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 0af3ba512bb..227f6c4a943 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -241,7 +241,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(r, bidReq) // move after merge - errL = deps.validateRequest(bidReq) + errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}) if errortypes.ContainsFatalError(errL) { handleError(&labels, w, errL, &vo, &debugLog) return diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9f0859a32cd..5452d6c2c39 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1087,11 +1087,13 @@ func TestCCPA(t *testing.T) { description string testFilePath string expectConsentString bool + expectEmptyConsent bool }{ { description: "Missing Consent", testFilePath: "sample-requests/video/video_valid_sample.json", expectConsentString: false, + expectEmptyConsent: true, }, { description: "Valid Consent", @@ -1132,7 +1134,7 @@ func TestCCPA(t *testing.T) { } if test.expectConsentString { assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent") - } else { + } else if test.expectEmptyConsent { assert.Empty(t, extRegs.USPrivacy, test.description+":consent") } diff --git a/exchange/utils.go b/exchange/utils.go index 0befeacdedd..120152466a8 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -28,18 +28,16 @@ var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{ const unknownBidder string = "" -func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { +func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) - if req != nil { - for _, schainWrapper := range req.Prebid.SChains { - for _, bidder := range schainWrapper.Bidders { - if _, present := bidderToSChains[bidder]; present { - return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ - "it must contain no more than one per bidder.", bidder) - } else { - bidderToSChains[bidder] = &schainWrapper.SChain - } + for _, schainWrapper := range sChains { + for _, bidder := range schainWrapper.Bidders { + if _, present := bidderToSChains[bidder]; present { + return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ + "it must contain no more than one per bidder.", bidder) + } else { + bidderToSChains[bidder] = &schainWrapper.SChain } } } @@ -178,7 +176,8 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT } func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { - ccpaPolicy, err := ccpa.ReadFromRequest(orig) + // Quick extra wrapper until RequestWrapper makes its way into CleanRequests + ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}) if err != nil { return privacy.NilPolicyEnforcer{}, err } @@ -217,9 +216,12 @@ func getAuctionBidderRequests(req AuctionRequest, var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain - sChainsByBidder, err = BidderToPrebidSChains(requestExt) - if err != nil { - return nil, []error{err} + // Quick extra wrapper until RequestWrapper makes its way into CleanRequests + if requestExt != nil { + sChainsByBidder, err = BidderToPrebidSChains(requestExt.Prebid.SChains) + if err != nil { + return nil, []error{err} + } } reqExt, err := getExtJson(req.BidRequest, requestExt) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5a9fa187f62..1d13928b59c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1908,7 +1908,7 @@ func TestBidderToPrebidChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 4) @@ -1934,7 +1934,7 @@ func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.NotNil(t, err) assert.Nil(t, output) @@ -1947,7 +1947,7 @@ func TestBidderToPrebidChainsNilSChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 0) @@ -1960,7 +1960,7 @@ func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 0) diff --git a/go.sum b/go.sum index 4cb5863dd41..df2bfb9b459 100644 --- a/go.sum +++ b/go.sum @@ -80,9 +80,11 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.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= github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index cc06f3806cf..1e8605562d2 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -70,8 +70,8 @@ type ExtDevicePrebid struct { // ExtDeviceInt defines the contract for bidrequest.device.ext.prebid.interstitial type ExtDeviceInt struct { - MinWidthPerc uint64 `json:"minwidtheperc"` - MinHeightPerc uint64 `json:"minheightperc"` + MinWidthPerc int64 `json:"minwidtheperc"` + MinHeightPerc int64 `json:"minheightperc"` } func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { @@ -85,7 +85,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { if err != nil || perc < 0 || perc > 100 { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100"} } - edi.MinWidthPerc = uint64(perc) + edi.MinWidthPerc = int64(perc) } if value, dataType, _, _ := jsonparser.Get(b, "minheightperc"); dataType != jsonparser.Number { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"} @@ -94,7 +94,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { if err != nil || perc < 0 || perc > 100 { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"} } - edi.MinHeightPerc = uint64(perc) + edi.MinHeightPerc = int64(perc) } return nil } diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go new file mode 100644 index 00000000000..276f8f5eebe --- /dev/null +++ b/openrtb_ext/request_wrapper.go @@ -0,0 +1,787 @@ +package openrtb_ext + +import ( + "encoding/json" + "errors" + + "github.com/mxmCherry/openrtb/v15/openrtb2" +) + +// RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they +// will not need to be unmarshalled multiple times. +// +// To start with, the wrapper can be created for a request 'req' via: +// reqWrapper := openrtb_ext.RequestWrapper{BidRequest: req} +// +// In order to access an object's ext field, fetch it via: +// userExt, err := reqWrapper.GetUserExt() +// or other Get method as appropriate. +// +// To read or write values, use the Ext objects Get and Set methods. If you need to write to a field that has its own Set +// method, use that to set the value rather than using SetExt() with that change done in the map; when rewritting the +// ext JSON the code will overwrite the the values in the map with the values stored in the seperate fields. +// +// userPrebid := userExt.GetPrebid() +// userExt.SetConsent(consentString) +// +// The GetExt() and SetExt() should only be used to access fields that have not already been resolved in the object. +// Using SetExt() at all is a strong hint that the ext object should be extended to support the new fields being set +// in the map. +// +// NOTE: The RequestWrapper methods (particularly the ones calling (un)Marshal are not thread safe) + +type RequestWrapper struct { + *openrtb2.BidRequest + userExt *UserExt + deviceExt *DeviceExt + requestExt *RequestExt + appExt *AppExt + regExt *RegExt + siteExt *SiteExt +} + +func (rw *RequestWrapper) GetUserExt() (*UserExt, error) { + if rw.userExt != nil { + return rw.userExt, nil + } + rw.userExt = &UserExt{} + if rw.BidRequest == nil || rw.User == nil || rw.User.Ext == nil { + return rw.userExt, rw.userExt.unmarshal(json.RawMessage{}) + } + + return rw.userExt, rw.userExt.unmarshal(rw.User.Ext) +} + +func (rw *RequestWrapper) GetDeviceExt() (*DeviceExt, error) { + if rw.deviceExt != nil { + return rw.deviceExt, nil + } + rw.deviceExt = &DeviceExt{} + if rw.BidRequest == nil || rw.Device == nil || rw.Device.Ext == nil { + return rw.deviceExt, rw.deviceExt.unmarshal(json.RawMessage{}) + } + return rw.deviceExt, rw.deviceExt.unmarshal(rw.Device.Ext) +} + +func (rw *RequestWrapper) GetRequestExt() (*RequestExt, error) { + if rw.requestExt != nil { + return rw.requestExt, nil + } + rw.requestExt = &RequestExt{} + if rw.BidRequest == nil || rw.Ext == nil { + return rw.requestExt, rw.requestExt.unmarshal(json.RawMessage{}) + } + return rw.requestExt, rw.requestExt.unmarshal(rw.Ext) +} + +func (rw *RequestWrapper) GetAppExt() (*AppExt, error) { + if rw.appExt != nil { + return rw.appExt, nil + } + rw.appExt = &AppExt{} + if rw.BidRequest == nil || rw.App == nil || rw.App.Ext == nil { + return rw.appExt, rw.appExt.unmarshal(json.RawMessage{}) + } + return rw.appExt, rw.appExt.unmarshal(rw.App.Ext) +} + +func (rw *RequestWrapper) GetRegExt() (*RegExt, error) { + if rw.regExt != nil { + return rw.regExt, nil + } + rw.regExt = &RegExt{} + if rw.BidRequest == nil || rw.Regs == nil || rw.Regs.Ext == nil { + return rw.regExt, rw.regExt.unmarshal(json.RawMessage{}) + } + return rw.regExt, rw.regExt.unmarshal(rw.Regs.Ext) +} + +func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) { + if rw.siteExt != nil { + return rw.siteExt, nil + } + rw.siteExt = &SiteExt{} + if rw.BidRequest == nil || rw.Site == nil || rw.Site.Ext == nil { + return rw.siteExt, rw.siteExt.unmarshal(json.RawMessage{}) + } + return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext) +} + +func (rw *RequestWrapper) RebuildRequest() error { + if rw.BidRequest == nil { + return errors.New("Requestwrapper Sync called on a nil BidRequest") + } + + if err := rw.rebuildUserExt(); err != nil { + return err + } + if err := rw.rebuildDeviceExt(); err != nil { + return err + } + if err := rw.rebuildRequestExt(); err != nil { + return err + } + if err := rw.rebuildAppExt(); err != nil { + return err + } + if err := rw.rebuildRegExt(); err != nil { + return err + } + if err := rw.rebuildSiteExt(); err != nil { + return err + } + + return nil +} + +func (rw *RequestWrapper) rebuildUserExt() error { + if rw.BidRequest.User == nil && rw.userExt != nil && rw.userExt.Dirty() { + rw.User = &openrtb2.User{} + } + if rw.userExt != nil && rw.userExt.Dirty() { + userJson, err := rw.userExt.marshal() + if err != nil { + return err + } + rw.User.Ext = userJson + } + return nil +} + +func (rw *RequestWrapper) rebuildDeviceExt() error { + if rw.Device == nil && rw.deviceExt != nil && rw.deviceExt.Dirty() { + rw.Device = &openrtb2.Device{} + } + if rw.deviceExt != nil && rw.deviceExt.Dirty() { + deviceJson, err := rw.deviceExt.marshal() + if err != nil { + return err + } + rw.Device.Ext = deviceJson + } + return nil +} + +func (rw *RequestWrapper) rebuildRequestExt() error { + if rw.requestExt != nil && rw.requestExt.Dirty() { + requestJson, err := rw.requestExt.marshal() + if err != nil { + return err + } + rw.Ext = requestJson + } + return nil +} + +func (rw *RequestWrapper) rebuildAppExt() error { + if rw.App == nil && rw.appExt != nil && rw.appExt.Dirty() { + rw.App = &openrtb2.App{} + } + if rw.appExt != nil && rw.appExt.Dirty() { + appJson, err := rw.appExt.marshal() + if err != nil { + return err + } + rw.App.Ext = appJson + } + return nil +} + +func (rw *RequestWrapper) rebuildRegExt() error { + if rw.Regs == nil && rw.regExt != nil && rw.regExt.Dirty() { + rw.Regs = &openrtb2.Regs{} + } + if rw.regExt != nil && rw.regExt.Dirty() { + regsJson, err := rw.regExt.marshal() + if err != nil { + return err + } + rw.Regs.Ext = regsJson + } + return nil +} + +func (rw *RequestWrapper) rebuildSiteExt() error { + if rw.Site == nil && rw.siteExt != nil && rw.siteExt.Dirty() { + rw.Site = &openrtb2.Site{} + } + if rw.siteExt != nil && rw.siteExt.Dirty() { + siteJson, err := rw.siteExt.marshal() + if err != nil { + return err + } + rw.Regs.Ext = siteJson + } + return nil +} + +// --------------------------------------------------------------- +// UserExt provides an interface for request.user.ext +// --------------------------------------------------------------- + +type UserExt struct { + ext map[string]json.RawMessage + extDirty bool + consent *string + consentDirty bool + prebid *ExtUserPrebid + prebidDirty bool + eids *[]ExtUserEid + eidsDirty bool +} + +func (ue *UserExt) unmarshal(extJson json.RawMessage) error { + if len(ue.ext) != 0 || ue.Dirty() { + return nil + } + ue.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + + if err := json.Unmarshal(extJson, &ue.ext); err != nil { + return err + } + + consentJson, hasConsent := ue.ext["consent"] + if hasConsent { + if err := json.Unmarshal(consentJson, &ue.consent); err != nil { + return err + } + } + + prebidJson, hasPrebid := ue.ext["prebid"] + if hasPrebid { + ue.prebid = &ExtUserPrebid{} + if err := json.Unmarshal(prebidJson, ue.prebid); err != nil { + return err + } + } + + eidsJson, hasEids := ue.ext["eids"] + if hasEids { + ue.eids = &[]ExtUserEid{} + if err := json.Unmarshal(eidsJson, ue.eids); err != nil { + return err + } + } + + return nil +} + +func (ue *UserExt) marshal() (json.RawMessage, error) { + if ue.consentDirty { + consentJson, err := json.Marshal(ue.consent) + if err != nil { + return nil, err + } + if len(consentJson) > 2 { + ue.ext["consent"] = json.RawMessage(consentJson) + } else { + delete(ue.ext, "consent") + } + ue.consentDirty = false + } + + if ue.prebidDirty { + prebidJson, err := json.Marshal(ue.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + ue.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(ue.ext, "prebid") + } + ue.prebidDirty = false + } + + if ue.eidsDirty { + if len(*ue.eids) > 0 { + eidsJson, err := json.Marshal(ue.eids) + if err != nil { + return nil, err + } + ue.ext["eids"] = json.RawMessage(eidsJson) + } else { + delete(ue.ext, "eids") + } + ue.eidsDirty = false + } + + ue.extDirty = false + if len(ue.ext) == 0 { + return nil, nil + } + return json.Marshal(ue.ext) + +} + +func (ue *UserExt) Dirty() bool { + return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty +} + +func (ue *UserExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range ue.ext { + ext[k] = v + } + return ext +} + +func (ue *UserExt) SetExt(ext map[string]json.RawMessage) { + ue.ext = ext + ue.extDirty = true +} + +func (ue *UserExt) GetConsent() *string { + if ue.consent == nil { + return nil + } + consent := *ue.consent + return &consent +} + +func (ue *UserExt) SetConsent(consent *string) { + ue.consent = consent + ue.consentDirty = true +} + +func (ue *UserExt) GetPrebid() *ExtUserPrebid { + if ue.prebid == nil { + return nil + } + prebid := *ue.prebid + return &prebid +} + +func (ue *UserExt) SetPrebid(prebid *ExtUserPrebid) { + ue.prebid = prebid + ue.prebidDirty = true +} + +func (ue *UserExt) GetEid() *[]ExtUserEid { + if ue.eids == nil { + return nil + } + eids := *ue.eids + return &eids +} + +func (ue *UserExt) SetEid(eid *[]ExtUserEid) { + ue.eids = eid + ue.eidsDirty = true +} + +// --------------------------------------------------------------- +// RequestExt provides an interface for request.ext +// --------------------------------------------------------------- + +type RequestExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtRequestPrebid + prebidDirty bool +} + +func (re *RequestExt) unmarshal(extJson json.RawMessage) error { + if len(re.ext) != 0 || re.Dirty() { + return nil + } + re.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &re.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := re.ext["prebid"] + if hasPrebid { + re.prebid = &ExtRequestPrebid{} + err = json.Unmarshal(prebidJson, re.prebid) + } + + return err +} + +func (re *RequestExt) marshal() (json.RawMessage, error) { + if re.prebidDirty { + prebidJson, err := json.Marshal(re.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + re.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(re.ext, "prebid") + } + re.prebidDirty = false + } + + re.extDirty = false + if len(re.ext) == 0 { + return nil, nil + } + return json.Marshal(re.ext) +} + +func (re *RequestExt) Dirty() bool { + return re.extDirty || re.prebidDirty +} + +func (re *RequestExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range re.ext { + ext[k] = v + } + return ext +} + +func (re *RequestExt) SetExt(ext map[string]json.RawMessage) { + re.ext = ext + re.extDirty = true +} + +func (re *RequestExt) GetPrebid() *ExtRequestPrebid { + if re.prebid == nil { + return nil + } + prebid := *re.prebid + return &prebid +} + +func (re *RequestExt) SetPrebid(prebid *ExtRequestPrebid) { + re.prebid = prebid + re.prebidDirty = true +} + +// --------------------------------------------------------------- +// DeviceExt provides an interface for request.device.ext +// --------------------------------------------------------------- +// NOTE: openrtb_ext/device.go:ParseDeviceExtATTS() uses ext.atts, as read only, via jsonparser, only for IOS. +// Doesn't seem like we will see any performance savings by parsing atts at this point, and as it is read only, +// we don't need to worry about write conflicts. Note here in case additional uses of atts evolve as things progress. +// --------------------------------------------------------------- + +type DeviceExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtDevicePrebid + prebidDirty bool +} + +func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { + if len(de.ext) != 0 || de.Dirty() { + return nil + } + de.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &de.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := de.ext["prebid"] + if hasPrebid { + de.prebid = &ExtDevicePrebid{} + err = json.Unmarshal(prebidJson, de.prebid) + } + + return err +} + +func (de *DeviceExt) marshal() (json.RawMessage, error) { + if de.prebidDirty { + prebidJson, err := json.Marshal(de.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + de.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(de.ext, "prebid") + } + de.prebidDirty = false + } + + de.extDirty = false + if len(de.ext) == 0 { + return nil, nil + } + return json.Marshal(de.ext) +} + +func (de *DeviceExt) Dirty() bool { + return de.extDirty || de.prebidDirty +} + +func (de *DeviceExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range de.ext { + ext[k] = v + } + return ext +} + +func (de *DeviceExt) SetExt(ext map[string]json.RawMessage) { + de.ext = ext + de.extDirty = true +} + +func (de *DeviceExt) GetPrebid() *ExtDevicePrebid { + if de.prebid == nil { + return nil + } + prebid := *de.prebid + return &prebid +} + +func (de *DeviceExt) SetPrebid(prebid *ExtDevicePrebid) { + de.prebid = prebid + de.prebidDirty = true +} + +// --------------------------------------------------------------- +// AppExt provides an interface for request.app.ext +// --------------------------------------------------------------- + +type AppExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtAppPrebid + prebidDirty bool +} + +func (ae *AppExt) unmarshal(extJson json.RawMessage) error { + if len(ae.ext) != 0 || ae.Dirty() { + return nil + } + ae.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &ae.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := ae.ext["prebid"] + if hasPrebid { + ae.prebid = &ExtAppPrebid{} + err = json.Unmarshal(prebidJson, ae.prebid) + } + + return err +} + +func (ae *AppExt) marshal() (json.RawMessage, error) { + if ae.prebidDirty { + prebidJson, err := json.Marshal(ae.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + ae.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(ae.ext, "prebid") + } + ae.prebidDirty = false + } + + ae.extDirty = false + if len(ae.ext) == 0 { + return nil, nil + } + return json.Marshal(ae.ext) +} + +func (ae *AppExt) Dirty() bool { + return ae.extDirty || ae.prebidDirty +} + +func (ae *AppExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range ae.ext { + ext[k] = v + } + return ext +} + +func (ae *AppExt) SetExt(ext map[string]json.RawMessage) { + ae.ext = ext + ae.extDirty = true +} + +func (ae *AppExt) GetPrebid() *ExtAppPrebid { + if ae.prebid == nil { + return nil + } + prebid := *ae.prebid + return &prebid +} + +func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) { + ae.prebid = prebid + ae.prebidDirty = true +} + +// --------------------------------------------------------------- +// RegExt provides an interface for request.regs.ext +// --------------------------------------------------------------- + +type RegExt struct { + ext map[string]json.RawMessage + extDirty bool + usPrivacy string + usPrivacyDirty bool +} + +func (re *RegExt) unmarshal(extJson json.RawMessage) error { + if len(re.ext) != 0 || re.Dirty() { + return nil + } + re.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &re.ext) + if err != nil { + return err + } + uspJson, hasUsp := re.ext["us_privacy"] + if hasUsp { + err = json.Unmarshal(uspJson, &re.usPrivacy) + } + + return err +} + +func (re *RegExt) marshal() (json.RawMessage, error) { + if re.usPrivacyDirty { + if len(re.usPrivacy) > 0 { + rawjson, err := json.Marshal(re.usPrivacy) + if err != nil { + return nil, err + } + re.ext["us_privacy"] = rawjson + } else { + delete(re.ext, "us_privacy") + } + re.usPrivacyDirty = false + } + + re.extDirty = false + if len(re.ext) == 0 { + return nil, nil + } + return json.Marshal(re.ext) +} + +func (re *RegExt) Dirty() bool { + return re.extDirty || re.usPrivacyDirty +} + +func (re *RegExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range re.ext { + ext[k] = v + } + return ext +} + +func (re *RegExt) SetExt(ext map[string]json.RawMessage) { + re.ext = ext + re.extDirty = true +} + +func (re *RegExt) GetUSPrivacy() string { + uSPrivacy := re.usPrivacy + return uSPrivacy +} + +func (re *RegExt) SetUSPrivacy(uSPrivacy string) { + re.usPrivacy = uSPrivacy + re.usPrivacyDirty = true +} + +// --------------------------------------------------------------- +// SiteExt provides an interface for request.site.ext +// --------------------------------------------------------------- + +type SiteExt struct { + ext map[string]json.RawMessage + extDirty bool + amp int8 + ampDirty bool +} + +func (se *SiteExt) unmarshal(extJson json.RawMessage) error { + if len(se.ext) != 0 || se.Dirty() { + return nil + } + se.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &se.ext) + if err != nil { + return err + } + AmpJson, hasAmp := se.ext["amp"] + if hasAmp { + err = json.Unmarshal(AmpJson, &se.amp) + if err != nil { + err = errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) + } + } + + return err +} + +func (se *SiteExt) marshal() (json.RawMessage, error) { + if se.ampDirty { + ampJson, err := json.Marshal(se.amp) + if err != nil { + return nil, err + } + if len(ampJson) > 2 { + se.ext["amp"] = json.RawMessage(ampJson) + } else { + delete(se.ext, "amp") + } + se.ampDirty = false + } + + se.extDirty = false + if len(se.ext) == 0 { + return nil, nil + } + return json.Marshal(se.ext) +} + +func (se *SiteExt) Dirty() bool { + return se.extDirty || se.ampDirty +} + +func (se *SiteExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range se.ext { + ext[k] = v + } + return ext +} + +func (se *SiteExt) SetExt(ext map[string]json.RawMessage) { + se.ext = ext + se.extDirty = true +} + +func (se *SiteExt) GetAmp() int8 { + return se.amp +} + +func (se *SiteExt) SetUSPrivacy(amp int8) { + se.amp = amp + se.ampDirty = true +} diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go new file mode 100644 index 00000000000..06cad49aedf --- /dev/null +++ b/openrtb_ext/request_wrapper_test.go @@ -0,0 +1,22 @@ +package openrtb_ext + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Some minimal tests to get code coverage above 30%. The real tests are when other modules use these structures. + +func TestUserExt(t *testing.T) { + userExt := &UserExt{} + + userExt.unmarshal(nil) + assert.Equal(t, false, userExt.Dirty(), "New UserExt should not be dirty.") + assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent") + + newConsent := "NewConsent" + userExt.SetConsent(&newConsent) + assert.Equal(t, "NewConsent", *userExt.GetConsent()) + +} diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 41f1c39447b..451f2b40238 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,8 +1,12 @@ package ccpa -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import ( + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" +) -// ConsentWriter implements the PolicyWriter interface for CCPA. +// ConsentWriter implements the old PolicyWriter interface for CCPA. +// This is used where we have not converted to RequestWrapper yet type ConsentWriter struct { Consent string } @@ -12,12 +16,11 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - - regs, err := buildRegs(c.Consent, req.Regs) - if err != nil { + reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} + if regsExt, err := reqWrap.GetRegExt(); err == nil { + regsExt.SetUSPrivacy(c.Consent) + } else { return err } - req.Regs = regs - - return nil + return reqWrap.RebuildRequest() } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index d59428626b8..28dfd41785e 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -5,10 +5,60 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) +// RegExt.SetUSPrivacy() is the new ConsentWriter func TestConsentWriter(t *testing.T) { + consent := "anyConsent" + testCases := []struct { + description string + request *openrtb2.BidRequest + expected *openrtb2.BidRequest + expectedError bool + }{ + { + description: "Nil Request", + request: nil, + expected: nil, + }, + { + description: "Success", + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + }, + }, + { + description: "Error With Regs.Ext - Does Not Mutate", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + expectedError: false, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + }, + } + + for _, test := range testCases { + + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + var err error + regsExt, err1 := reqWrapper.GetRegExt() + if err1 == nil { + regsExt.SetUSPrivacy(consent) + if reqWrapper.BidRequest != nil { + err = reqWrapper.RebuildRequest() + } + } + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description) + } +} + +func TestConsentWriterLegacy(t *testing.T) { consent := "anyConsent" testCases := []struct { description string diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index d57ba8deaa4..39322317df5 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -1,8 +1,6 @@ package ccpa import ( - "encoding/json" - "errors" "fmt" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -15,8 +13,8 @@ type Policy struct { NoSaleBidders []string } -// ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request. -func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { +// ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request. +func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper) (Policy, error) { var consent string var noSaleBidders []string @@ -25,174 +23,80 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { } // Read consent from request.regs.ext - if req.Regs != nil && len(req.Regs.Ext) > 0 { - var ext openrtb_ext.ExtRegs - if err := json.Unmarshal(req.Regs.Ext, &ext); err != nil { - return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) - } - consent = ext.USPrivacy + regsExt, err := req.GetRegExt() + if err != nil { + return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) + } + if regsExt != nil { + consent = regsExt.GetUSPrivacy() } - // Read no sale bidders from request.ext.prebid - if len(req.Ext) > 0 { - var ext openrtb_ext.ExtRequest - if err := json.Unmarshal(req.Ext, &ext); err != nil { - return Policy{}, fmt.Errorf("error reading request.ext.prebid: %s", err) - } - noSaleBidders = ext.Prebid.NoSale + reqExt, err := req.GetRequestExt() + if err != nil { + return Policy{}, fmt.Errorf("error reading request.ext: %s", err) + } + reqPrebid := reqExt.GetPrebid() + if reqPrebid != nil { + noSaleBidders = reqPrebid.NoSale } return Policy{consent, noSaleBidders}, nil } +func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { + return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req}) +} + // Write mutates an OpenRTB bid request with the CCPA regulatory information. -func (p Policy) Write(req *openrtb2.BidRequest) error { +func (p Policy) Write(req *openrtb_ext.RequestWrapper) error { if req == nil { return nil } - regs, err := buildRegs(p.Consent, req.Regs) + regsExt, err := req.GetRegExt() if err != nil { return err } - ext, err := buildExt(p.NoSaleBidders, req.Ext) + + reqExt, err := req.GetRequestExt() if err != nil { return err } - req.Regs = regs - req.Ext = ext + regsExt.SetUSPrivacy(p.Consent) + setPrebidNoSale(p.NoSaleBidders, reqExt) return nil } -func buildRegs(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if consent == "" { - return buildRegsClear(regs) - } - return buildRegsWrite(consent, regs) -} - -func buildRegsClear(regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if regs == nil || len(regs.Ext) == 0 { - return regs, nil - } - - var extMap map[string]interface{} - if err := json.Unmarshal(regs.Ext, &extMap); err != nil { - return nil, err - } - - delete(extMap, "us_privacy") - - // Remove entire ext if it's now empty - if len(extMap) == 0 { - regsResult := *regs - regsResult.Ext = nil - return ®sResult, nil - } - - // Marshal ext if there are still other fields - var regsResult openrtb2.Regs - ext, err := json.Marshal(extMap) - if err == nil { - regsResult = *regs - regsResult.Ext = ext - } - return ®sResult, err -} - -func buildRegsWrite(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if regs == nil { - return marshalRegsExt(openrtb2.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) - } - - if regs.Ext == nil { - return marshalRegsExt(*regs, openrtb_ext.ExtRegs{USPrivacy: consent}) - } - - var extMap map[string]interface{} - if err := json.Unmarshal(regs.Ext, &extMap); err != nil { - return nil, err - } - - extMap["us_privacy"] = consent - return marshalRegsExt(*regs, extMap) -} - -func marshalRegsExt(regs openrtb2.Regs, ext interface{}) (*openrtb2.Regs, error) { - extJSON, err := json.Marshal(ext) - if err == nil { - regs.Ext = extJSON - } - return ®s, err -} - -func buildExt(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { +func setPrebidNoSale(noSaleBidders []string, ext *openrtb_ext.RequestExt) { if len(noSaleBidders) == 0 { - return buildExtClear(ext) + setPrebidNoSaleClear(ext) + } else { + setPrebidNoSaleWrite(noSaleBidders, ext) } - return buildExtWrite(noSaleBidders, ext) } -func buildExtClear(ext json.RawMessage) (json.RawMessage, error) { - if len(ext) == 0 { - return ext, nil - } - - var extMap map[string]interface{} - if err := json.Unmarshal(ext, &extMap); err != nil { - return nil, err - } - - prebidExt, exists := extMap["prebid"] - if !exists { - return ext, nil - } - - // Verify prebid is an object - prebidExtMap, ok := prebidExt.(map[string]interface{}) - if !ok { - return nil, errors.New("request.ext.prebid is not a json object") +func setPrebidNoSaleClear(ext *openrtb_ext.RequestExt) { + prebid := ext.GetPrebid() + if prebid == nil { + return } // Remove no sale member - delete(prebidExtMap, "nosale") - if len(prebidExtMap) == 0 { - delete(extMap, "prebid") - } - - // Remove entire ext if it's empty - if len(extMap) == 0 { - return nil, nil - } - - return json.Marshal(extMap) + prebid.NoSale = []string{} + ext.SetPrebid(prebid) } -func buildExtWrite(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { - if len(ext) == 0 { - return json.Marshal(openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{NoSale: noSaleBidders}}) - } - - var extMap map[string]interface{} - if err := json.Unmarshal(ext, &extMap); err != nil { - return nil, err +func setPrebidNoSaleWrite(noSaleBidders []string, ext *openrtb_ext.RequestExt) { + if ext == nil { + // This should hopefully not be possible. The only caller insures that this has been initialized + return } - var prebidExt map[string]interface{} - if prebidExtInterface, exists := extMap["prebid"]; exists { - // Reference Existing Prebid Ext Map - if prebidExtMap, ok := prebidExtInterface.(map[string]interface{}); ok { - prebidExt = prebidExtMap - } else { - return nil, errors.New("request.ext.prebid is not a json object") - } - } else { - // Create New Empty Prebid Ext Map - prebidExt = make(map[string]interface{}) - extMap["prebid"] = prebidExt + prebid := ext.GetPrebid() + if prebid == nil { + prebid = &openrtb_ext.ExtRequestPrebid{} } - - prebidExt["nosale"] = noSaleBidders - return json.Marshal(extMap) + prebid.NoSale = noSaleBidders + ext.SetPrebid(prebid) } diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 416ebffa31a..ca6d0f8acf2 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -155,7 +156,8 @@ func TestReadFromRequest(t *testing.T) { } for _, test := range testCases { - result, err := ReadFromRequest(test.request) + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + result, err := ReadFromRequestWrapper(reqWrapper) assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expectedPolicy, result, test.description) } @@ -209,9 +211,20 @@ func TestWrite(t *testing.T) { } for _, test := range testCases { - err := test.policy.Write(test.request) + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + var err error + _, err = reqWrapper.GetRegExt() + if err == nil { + _, err = reqWrapper.GetRequestExt() + if err == nil { + err = test.policy.Write(reqWrapper) + if err == nil && reqWrapper.BidRequest != nil { + err = reqWrapper.RebuildRequest() + } + } + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, test.request, test.description) + assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description) } } @@ -237,6 +250,9 @@ func TestBuildRegs(t *testing.T) { regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, + expected: &openrtb2.Regs{ + Ext: json.RawMessage(`malformed`), + }, expectedError: true, }, { @@ -253,14 +269,22 @@ func TestBuildRegs(t *testing.T) { regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, + expected: &openrtb2.Regs{ + Ext: json.RawMessage(`malformed`), + }, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegs(test.consent, test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy(test.consent) + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -274,7 +298,7 @@ func TestBuildRegsClear(t *testing.T) { { description: "Nil Regs", regs: nil, - expected: nil, + expected: &openrtb2.Regs{Ext: nil}, }, { description: "Nil Regs.Ext", @@ -297,21 +321,28 @@ func TestBuildRegsClear(t *testing.T) { expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, }, { - description: "Invalid Regs.Ext Type - Still Cleared", - regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb2.Regs{}, + description: "Invalid Regs.Ext Type - Returns Error, doesn't clear", + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expectedError: true, }, { description: "Malformed Regs.Ext", regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegsClear(test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy("") + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -354,23 +385,30 @@ func TestBuildRegsWrite(t *testing.T) { expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Regs.Ext Type - Still Overwrites", - consent: "anyConsent", - regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + description: "Invalid Regs.Ext Type - Doesn't Overwrite", + consent: "anyConsent", + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expectedError: true, }, { description: "Malformed Regs.Ext", consent: "anyConsent", regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegsWrite(test.consent, test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy(test.consent) + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -415,7 +453,14 @@ func TestBuildExt(t *testing.T) { } for _, test := range testCases { - result, err := buildExt(test.noSaleBidders, test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSale(test.noSaleBidders, reqExt) + err = request.RebuildRequest() + result = request.Ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } @@ -460,13 +505,13 @@ func TestBuildExtClear(t *testing.T) { }, { description: "Leaves Other Ext.Prebid Values", - ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), - expected: json.RawMessage(`{"prebid":{"other":"any"}}`), + ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"aliases":{"a":"b"}}}`), + expected: json.RawMessage(`{"prebid":{"aliases":{"a":"b"}}}`), }, { description: "Leaves All Other Values", - ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), - expected: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), + ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"supportdeals":true}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"supportdeals":true}}`), }, { description: "Malformed Ext", @@ -486,7 +531,14 @@ func TestBuildExtClear(t *testing.T) { } for _, test := range testCases { - result, err := buildExtClear(test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSaleClear(reqExt) + err = request.RebuildRequest() + result = request.Ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } @@ -539,43 +591,56 @@ func TestBuildExtWrite(t *testing.T) { { description: "Leaves Other Ext.Prebid Values", noSaleBidders: []string{"a", "b"}, - ext: json.RawMessage(`{"prebid":{"other":"any"}}`), - expected: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), + ext: json.RawMessage(`{"prebid":{"supportdeals":true}}`), + expected: json.RawMessage(`{"prebid":{"supportdeals":true,"nosale":["a","b"]}}`), }, { description: "Leaves All Other Values", noSaleBidders: []string{"a", "b"}, - ext: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), - expected: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), + ext: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"}}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"},"nosale":["a","b"]}}`), }, { description: "Invalid Ext.Prebid No Sale Type - Still Overrides", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":{"nosale":123}}`), - expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + expected: json.RawMessage(`{"prebid":{"nosale":123}}`), + expectedError: true, }, { description: "Invalid Ext.Prebid Type ", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":"wrongtype"}`), + expected: json.RawMessage(`{"prebid":"wrongtype"}`), expectedError: true, }, { description: "Malformed Ext", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{malformed`), + expected: json.RawMessage(`{malformed`), expectedError: true, }, { description: "Malformed Ext.Prebid", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":malformed}`), + expected: json.RawMessage(`{"prebid":malformed}`), expectedError: true, }, } for _, test := range testCases { - result, err := buildExtWrite(test.noSaleBidders, test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSaleWrite(test.noSaleBidders, reqExt) + err = request.RebuildRequest() + result = request.Ext + } else { + result = test.ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } From 5ec40b9262a8c7ee75c397a5a088e1fc2ec7012e Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Wed, 21 Jul 2021 21:55:11 +0300 Subject: [PATCH 463/603] Rubicon: Use currency conversion function (#1924) Co-authored-by: Serhii Nahornyi --- adapters/bidder.go | 6 +- adapters/rubicon/rubicon.go | 28 ++-- adapters/rubicon/rubicon_test.go | 131 ++++++++++++++---- .../rubicontest/exemplary/simple-video.json | 4 - .../supplemental/no-site-content-data.json | 4 - .../supplemental/no-site-content.json | 4 - 6 files changed, 123 insertions(+), 54 deletions(-) diff --git a/adapters/bidder.go b/adapters/bidder.go index b7bde4bc55d..dbe56f8eb91 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -139,12 +139,12 @@ func (r *RequestData) SetBasicAuth(username string, password string) { type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string - currencyConversions currency.Conversions + CurrencyConversions currency.Conversions } func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { return ExtraRequestInfo{ - currencyConversions: c, + CurrencyConversions: c, } } @@ -153,7 +153,7 @@ func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { // - ConversionNotFoundError if the conversion mapping is unknown to Prebid Server // and not provided in the bid request. func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float64, error) { - if rate, err := r.currencyConversions.GetRate(from, to); err == nil { + if rate, err := r.CurrencyConversions.GetRate(from, to); err == nil { return value * rate, nil } else { return 0, err diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 84200431992..e9627916cc6 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -676,7 +676,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) var err error - requestData := make([]*adapters.RequestData, 0, numRequests) headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") @@ -750,9 +749,19 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } - resolvedBidFloor, resolvedBidFloorCur := resolveBidFloorAttributes(thisImp.BidFloor, thisImp.BidFloorCur) - thisImp.BidFloorCur = resolvedBidFloorCur - thisImp.BidFloor = resolvedBidFloor + resolvedBidFloor, err := resolveBidFloor(thisImp.BidFloor, thisImp.BidFloorCur, reqInfo) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", + thisImp.BidFloorCur), + }) + continue + } + + if resolvedBidFloor > 0 { + thisImp.BidFloorCur = "USD" + thisImp.BidFloor = resolvedBidFloor + } if request.User != nil { userCopy := *request.User @@ -908,15 +917,12 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } -// Will be replaced after https://github.com/prebid/prebid-server/issues/1482 resolution -func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, string) { - if bidFloor > 0 { - if strings.ToUpper(bidFloorCur) == "EUR" { - return bidFloor * 1.2, "USD" - } +func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { + if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != "USD" { + return reqInfo.ConvertCurrency(bidFloor, bidFloorCur, "USD") } - return bidFloor, bidFloorCur + return bidFloor, nil } func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 76904a42137..28ddebbf5e3 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "encoding/json" + "errors" + "github.com/stretchr/testify/mock" "io/ioutil" "net/http" "net/http/httptest" @@ -572,52 +574,125 @@ func TestResolveVideoSizeId(t *testing.T) { } } -func TestResolveBidFloorAttributes(t *testing.T) { +func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) { testScenarios := []struct { - bidFloor float64 - bidFloorCur string - expectedBidFloor float64 - expectedBidFloorCur string + bidFloor float64 + bidFloorCur string + setMock func(m *mock.Mock) + expectedBidFloor float64 + expectedBidCur string + expectedErrors []error }{ { - bidFloor: 1, - bidFloorCur: "EUR", - expectedBidFloor: 1.2, - expectedBidFloorCur: "USD", + bidFloor: 1, + bidFloorCur: "WRONG", + setMock: func(m *mock.Mock) { m.On("GetRate", "WRONG", "USD").Return(2.5, errors.New("some error")) }, + expectedBidFloor: 0, + expectedBidCur: "", + expectedErrors: []error{ + &errortypes.BadInput{Message: "Unable to convert provided bid floor currency from WRONG to USD"}, + }, }, { - bidFloor: 1, - bidFloorCur: "Eur", - expectedBidFloor: 1.2, - expectedBidFloorCur: "USD", + bidFloor: 1, + bidFloorCur: "USD", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 1, + expectedBidCur: "USD", + expectedErrors: nil, }, { - bidFloor: 0, - bidFloorCur: "EUR", - expectedBidFloor: 0, - expectedBidFloorCur: "EUR", + bidFloor: 1, + bidFloorCur: "EUR", + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USD").Return(1.2, nil) }, + expectedBidFloor: 1.2, + expectedBidCur: "USD", + expectedErrors: nil, }, { - bidFloor: -1, - bidFloorCur: "EUR", - expectedBidFloor: -1, - expectedBidFloorCur: "EUR", + bidFloor: 0, + bidFloorCur: "", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 0, + expectedBidCur: "", + expectedErrors: nil, }, { - bidFloor: 1, - bidFloorCur: "USD", - expectedBidFloor: 1, - expectedBidFloorCur: "USD", + bidFloor: -1, + bidFloorCur: "CZK", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: -1, + expectedBidCur: "CZK", + expectedErrors: nil, }, } for _, scenario := range testScenarios { - bidFloor, bidFloorCur := resolveBidFloorAttributes(scenario.bidFloor, scenario.bidFloorCur) - assert.Equal(t, scenario.expectedBidFloor, bidFloor) - assert.Equal(t, scenario.expectedBidFloorCur, bidFloorCur) + mockConversions := &mockCurrencyConversion{} + scenario.setMock(&mockConversions.Mock) + + extraRequestInfo := adapters.ExtraRequestInfo{ + CurrencyConversions: mockConversions, + } + + SIZE_ID := getTestSizes() + bidder := new(RubiconAdapter) + + request := &openrtb2.BidRequest{ + ID: "test-request-id", + Imp: []openrtb2.Imp{{ + ID: "test-imp-id", + BidFloorCur: scenario.bidFloorCur, + BidFloor: scenario.bidFloor, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + SIZE_ID[15], + SIZE_ID[10], + }, + }, + Ext: json.RawMessage(`{"bidder": { + "zoneId": 8394, + "siteId": 283282, + "accountId": 7891 + }}`), + }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, + } + + reqs, errs := bidder.MakeRequests(request, &extraRequestInfo) + + mockConversions.AssertExpectations(t) + + if scenario.expectedErrors == nil { + rubiconReq := &openrtb2.BidRequest{} + if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { + t.Fatalf("Unexpected error while decoding request: %s", err) + } + assert.Equal(t, scenario.expectedBidFloor, rubiconReq.Imp[0].BidFloor) + assert.Equal(t, scenario.expectedBidCur, rubiconReq.Imp[0].BidFloorCur) + } else { + assert.Equal(t, scenario.expectedErrors, errs) + } } } +type mockCurrencyConversion struct { + mock.Mock +} + +func (m mockCurrencyConversion) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} + func TestNoContentResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 11afdd50d2b..1daffe9b386 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -120,8 +120,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -298,8 +296,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json index f67788a3154..0be214da4bc 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -85,8 +85,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -221,8 +219,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json index d3b8f8b7454..2e830a2dd00 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -83,8 +83,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -217,8 +215,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { From ba1fe79ae1719c87a791329920808dabaa57b295 Mon Sep 17 00:00:00 2001 From: jizeyopera <70930512+jizeyopera@users.noreply.github.com> Date: Thu, 22 Jul 2021 06:35:46 +0800 Subject: [PATCH 464/603] New Adapter: operaads (#1916) --- adapters/operaads/operaads.go | 215 ++++++++++++++++++ adapters/operaads/operaads_test.go | 20 ++ .../operaadstest/exemplary/native.json | 137 +++++++++++ .../operaadstest/exemplary/simple-banner.json | 156 +++++++++++++ .../operaadstest/exemplary/video.json | 173 ++++++++++++++ .../operaadstest/supplemental/badrequest.json | 84 +++++++ .../supplemental/banner-size-miss.json | 50 ++++ .../supplemental/miss-native.json | 137 +++++++++++ .../supplemental/missing-device.json | 33 +++ .../operaadstest/supplemental/nocontent.json | 82 +++++++ .../supplemental/unexcept-statuscode.json | 84 +++++++ adapters/operaads/params_test.go | 54 +++++ adapters/operaads/usersync.go | 11 + adapters/operaads/usersync_test.go | 33 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_operaads.go | 7 + static/bidder-info/operaads.yaml | 14 ++ static/bidder-params/operaads.json | 28 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1327 insertions(+) create mode 100644 adapters/operaads/operaads.go create mode 100644 adapters/operaads/operaads_test.go create mode 100644 adapters/operaads/operaadstest/exemplary/native.json create mode 100644 adapters/operaads/operaadstest/exemplary/simple-banner.json create mode 100644 adapters/operaads/operaadstest/exemplary/video.json create mode 100644 adapters/operaads/operaadstest/supplemental/badrequest.json create mode 100644 adapters/operaads/operaadstest/supplemental/banner-size-miss.json create mode 100644 adapters/operaads/operaadstest/supplemental/miss-native.json create mode 100644 adapters/operaads/operaadstest/supplemental/missing-device.json create mode 100644 adapters/operaads/operaadstest/supplemental/nocontent.json create mode 100644 adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json create mode 100644 adapters/operaads/params_test.go create mode 100644 adapters/operaads/usersync.go create mode 100644 adapters/operaads/usersync_test.go create mode 100644 openrtb_ext/imp_operaads.go create mode 100644 static/bidder-info/operaads.yaml create mode 100644 static/bidder-params/operaads.json diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go new file mode 100644 index 00000000000..890a15ddb5f --- /dev/null +++ b/adapters/operaads/operaads.go @@ -0,0 +1,215 @@ +package operaads + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/macros" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + epTemplate *template.Template +} + +// Builder builds a new instance of the operaads adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + epTemplate, err := template.New("endpoint").Parse(config.Endpoint) + if err != nil { + return nil, err + } + bidder := &adapter{ + epTemplate: epTemplate, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impCount := len(request.Imp) + requestData := make([]*adapters.RequestData, 0, impCount) + errs := []error{} + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + err := checkRequest(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + for _, imp := range request.Imp { + requestCopy := *request + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var operaadsExt openrtb_ext.ImpExtOperaads + if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + err := convertImpression(&imp) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = operaadsExt.PlacementID + + requestCopy.Imp = []openrtb2.Imp{imp} + reqJSON, err := json.Marshal(&requestCopy) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID} + endpoint, err := macros.ResolveMacros(*a.epTemplate, ¯o) + if err != nil { + errs = append(errs, err) + continue + } + reqData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: endpoint, + Body: reqJSON, + Headers: headers, + } + requestData = append(requestData, reqData) + } + return requestData, errs +} + +func checkRequest(request *openrtb2.BidRequest) error { + if request.Device == nil || len(request.Device.OS) == 0 { + return &errortypes.BadInput{ + Message: "Impression is missing device OS information", + } + } + + return nil +} + +func convertImpression(imp *openrtb2.Imp) error { + if imp.Banner != nil { + bannerCopy, err := convertBanner(imp.Banner) + if err != nil { + return err + } + imp.Banner = bannerCopy + } + if imp.Native != nil && imp.Native.Request != "" { + v := make(map[string]interface{}) + err := json.Unmarshal([]byte(imp.Native.Request), &v) + if err != nil { + return err + } + _, ok := v["native"] + if !ok { + body, err := json.Marshal(struct { + Native interface{} `json:"native"` + }{ + Native: v, + }) + if err != nil { + return err + } + native := *imp.Native + native.Request = string(body) + imp.Native = &native + } + } + return nil +} + +// make sure that banner has openrtb 2.3-compatible size information +func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { + if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { + if len(banner.Format) > 0 { + f := banner.Format[0] + + bannerCopy := *banner + + bannerCopy.W = openrtb2.Int64Ptr(f.W) + bannerCopy.H = openrtb2.Int64Ptr(f.H) + + return &bannerCopy, nil + } else { + return nil, &errortypes.BadInput{ + Message: "Size information missing for banner", + } + } + } + return banner, nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var parsedResponse openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range parsedResponse.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + if bid.Price != 0 { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + }) + } + } + } + return bidResponse, nil +} + +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } + return mediaType + } + } + return mediaType +} diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go new file mode 100644 index 00000000000..eb4280b68e9 --- /dev/null +++ b/adapters/operaads/operaads_test.go @@ -0,0 +1,20 @@ +package operaads + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOperaads, config.Adapter{ + Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "operaadstest", bidder) +} diff --git a/adapters/operaads/operaadstest/exemplary/native.json b/adapters/operaads/operaadstest/exemplary/native.json new file mode 100644 index 00000000000..4491bd150e4 --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/native.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"some-req-id", + "seatbid":[ + { + "bid":[ + { + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adid":"69595837", + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + } + ], + "seat":"958" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses":[ + { + "currency":"USD", + "bids":[ + { + "bid":{ + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", + "adid":"69595837", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + }, + "type":"native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/exemplary/simple-banner.json b/adapters/operaads/operaadstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..53b19c82c2a --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/simple-banner.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub456?ep=ep19979", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "s17890", + "banner": { + "h": 250, + "w": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "1519967420713_259406708_583019428", + "seatbid": [ + { + "bid": [ + { + "price": 0.5, + "adm": "some-test-ad", + "impid": "1", + "auid": 46, + "id": "1", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "crid": "11_222222", + "w": 300 + } + ], + "seat": "51" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids": [ + { + "bid": { + "price": 0.5, + "adm": "some-test-ad", + "impid": "1", + "id": "1", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "crid": "11_222222", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/operaads/operaadstest/exemplary/video.json b/adapters/operaads/operaadstest/exemplary/video.json new file mode 100644 index 00000000000..a76bbe5ccf8 --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/video.json @@ -0,0 +1,173 @@ +{ + "mockBidRequest": { + "id": "test-video-request", + "imp": [ + { + "id": "test-video-imp", + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4", + "video/x-flv" + ], + "minduration": 5, + "maxduration": 30, + "startdelay": 5, + "playbackmethod": [ + 1, + 3 + ], + "api": [ + 1, + 2 + ], + "protocols": [ + 2, + 3 + ], + "battr": [ + 13, + 14 + ], + "linearity": 1, + "placement": 2, + "minbitrate": 10, + "maxbitrate": 10 + }, + "ext": { + "bidder": { + "placementId":"s00000", + "endpointId":"ep00000", + "publisherId":"pub00000" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "os": "android" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub00000?ep=ep00000", + "body": { + "id": "test-video-request", + "imp": [ + { + "id": "test-video-imp", + "tagid": "s00000", + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4", + "video/x-flv" + ], + "minduration": 5, + "maxduration": 30, + "startdelay": 5, + "playbackmethod": [ + 1, + 3 + ], + "api": [ + 1, + 2 + ], + "protocols": [ + 2, + 3 + ], + "battr": [ + 13, + 14 + ], + "linearity": 1, + "placement": 2, + "minbitrate": 10, + "maxbitrate": 10 + }, + "ext": { + "bidder": { + "placementId":"s00000", + "endpointId":"ep00000", + "publisherId":"pub00000" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "os": "android" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-video-request", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-video-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "example.com" + ], + "crid": "29681110", + "h": 250, + "w": 300 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-video-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "example.com" + ], + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/badrequest.json b/adapters/operaads/operaadstest/supplemental/badrequest.json new file mode 100644 index 00000000000..ff3fe071c4a --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/badrequest.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":400, + "body":{} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/supplemental/banner-size-miss.json b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json new file mode 100644 index 00000000000..70ac350c2f0 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Size information missing for banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/miss-native.json b/adapters/operaads/operaadstest/supplemental/miss-native.json new file mode 100644 index 00000000000..918bbc4ded5 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/miss-native.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"img\":{\"hmin\":194,\"type\":3,\"wmin\":344},\"required\":1},{\"id\":3,\"img\":{\"h\":128,\"hmin\":80,\"type\":1,\"w\":128,\"wmin\":80},\"required\":1},{\"data\":{\"len\":90,\"type\":2},\"id\":4,\"required\":1},{\"data\":{\"len\":15,\"type\":12},\"id\":6}],\"layout\":3,\"ver\":\"1.1\"}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"some-req-id", + "seatbid":[ + { + "bid":[ + { + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adid":"69595837", + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + } + ], + "seat":"958" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses":[ + { + "currency":"USD", + "bids":[ + { + "bid":{ + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", + "adid":"69595837", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + }, + "type":"native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/supplemental/missing-device.json b/adapters/operaads/operaadstest/supplemental/missing-device.json new file mode 100644 index 00000000000..3ba01d81632 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/missing-device.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Impression is missing device OS information", + "comparison": "literal" + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/nocontent.json b/adapters/operaads/operaadstest/supplemental/nocontent.json new file mode 100644 index 00000000000..d6baf35d4a6 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/nocontent.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "some-req-id", + "imp": [ + { + "id": "some-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "s123456", + "endpointId": "ep19978", + "publisherId": "pub123" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "example.com" + }, + "device": { + "ip": "152.193.6.74", + "os": "android" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "8299345306627569435" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body": { + "id": "some-req-id", + "imp": [ + { + "id": "some-imp-id", + "tagid": "s123456", + "ext": { + "bidder": { + "placementId": "s123456", + "endpointId": "ep19978", + "publisherId": "pub123" + } + }, + "native": { + "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver": "1.1" + } + } + ], + "site": { + "domain": "example.com", + "page": "example.com" + }, + "device": { + "ip": "152.193.6.74", + "os": "android" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "8299345306627569435" + }, + "tmax": 500 + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeBidsErrors": [ + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json new file mode 100644 index 00000000000..a6c8c5052ae --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":205, + "body":{} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 205. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go new file mode 100644 index 00000000000..e998127b001 --- /dev/null +++ b/adapters/operaads/params_test.go @@ -0,0 +1,54 @@ +package operaads + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/operaads.json +// +// These also validate the format of the external API: request.imp[i].ext.operaads + +// TestValidParams makes sure that the operaads schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderOperaads, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected operaads params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the operaads schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderOpenx, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": "s123", "endpointId": "ep12345", "publisherId": "pub12345"}`, +} + +var invalidParams = []string{ + `{"placementId": "s123"}`, + `{"endpointId": "ep12345"}`, + `{"publisherId": "pub12345"}`, + `{"placementId": "s123", "endpointId": "ep12345"}`, + `{"placementId": "s123", "publisherId": "pub12345"}`, + `{"endpointId": "ep12345", "publisherId": "pub12345"}`, + `{"placementId": "", "endpointId": "", "publisherId": ""}`, +} diff --git a/adapters/operaads/usersync.go b/adapters/operaads/usersync.go new file mode 100644 index 00000000000..aae6fb600e3 --- /dev/null +++ b/adapters/operaads/usersync.go @@ -0,0 +1,11 @@ +package operaads + +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" + "text/template" +) + +func NewOperaadsSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("operaads", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/operaads/usersync_test.go b/adapters/operaads/usersync_test.go new file mode 100644 index 00000000000..e9b402ac465 --- /dev/null +++ b/adapters/operaads/usersync_test.go @@ -0,0 +1,33 @@ +package operaads + +import ( + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestOperaadsSyncer(t *testing.T) { + syncURL := "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewOperaadsSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Consent: "C", + }}) + + assert.NoError(t, err) + assert.Equal(t, "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr=A&gdpr_consent=B&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUID%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index b64bab0006f..b8ff3bd6116 100644 --- a/config/config.go +++ b/config/config.go @@ -663,6 +663,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOperaads, "https://t.adx.opera.com/pbs/sync?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOutbrain, "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doutbrain%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") @@ -930,6 +931,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1") v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") + v.SetDefault("adapters.operaads.endpoint", "https://s.adx.opera.com/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}") v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.pangle.disabled", true) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 9adc9ebc671..1d91fadd96d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -80,6 +80,7 @@ import ( "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openx" + "github.com/prebid/prebid-server/adapters/operaads" "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/pangle" @@ -206,6 +207,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenx: openx.Builder, + openrtb_ext.BidderOperaads: operaads.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderPangle: pangle.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 780b75f60b8..8a589bd1a74 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -153,6 +153,7 @@ const ( BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" BidderOpenx BidderName = "openx" + BidderOperaads BidderName = "operaads" BidderOrbidder BidderName = "orbidder" BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" @@ -277,6 +278,7 @@ func CoreBidderNames() []BidderName { BidderNoBid, BidderOneTag, BidderOpenx, + BidderOperaads, BidderOrbidder, BidderOutbrain, BidderPangle, diff --git a/openrtb_ext/imp_operaads.go b/openrtb_ext/imp_operaads.go new file mode 100644 index 00000000000..99ccd7c431b --- /dev/null +++ b/openrtb_ext/imp_operaads.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ImpExtOperaads struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` + PublisherID string `json:"publisherId"` +} diff --git a/static/bidder-info/operaads.yaml b/static/bidder-info/operaads.yaml new file mode 100644 index 00000000000..b95d81155c1 --- /dev/null +++ b/static/bidder-info/operaads.yaml @@ -0,0 +1,14 @@ +maintainer: + email: adtech-prebid-group@opera.com +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/operaads.json b/static/bidder-params/operaads.json new file mode 100644 index 00000000000..5095c5b2d2b --- /dev/null +++ b/static/bidder-params/operaads.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A schema which validates params accepted by the OperaAds adapter", + "properties": { + "placementId": { + "description": "Placement ID", + "type": "string", + "minLength": 1 + }, + "endpointId": { + "description": "Endpoint ID", + "type": "string", + "minLength": 1 + }, + "publisherId": { + "description": "Publisher ID", + "type": "string", + "minLength": 1 + } + }, + "required": [ + "placementId", + "endpointId", + "publisherId" + ], + "title": "OperaAds Adapter Params", + "type": "object" +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 53472018e30..674fc136527 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -1,6 +1,7 @@ package usersyncers import ( + "github.com/prebid/prebid-server/adapters/operaads" "strings" "text/template" @@ -169,6 +170,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOutbrain, outbrain.NewOutbrainSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOperaads, operaads.NewOperaadsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderRhythmone, rhythmone.NewRhythmoneSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 59ed67cca0b..8a2a3e5d2e1 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -75,6 +75,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderNoBid): syncConfig, string(openrtb_ext.BidderOneTag): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, + string(openrtb_ext.BidderOperaads): syncConfig, string(openrtb_ext.BidderOutbrain): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig, From 0aa427ebccffa042ab9242dacbfe32a90f5f9afa Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Thu, 22 Jul 2021 15:29:35 -0400 Subject: [PATCH 465/603] Fix Beachfront data race condition (#1915) Co-authored-by: Jim Naumann --- adapters/beachfront/beachfront.go | 5 +- .../exemplary/adm-video-app.json | 124 ++++++++++++++++++ .../adm-video-app-alphanum-bundle.json | 123 +++++++++++++++++ .../adm-video-app-malformed-bundle.json | 124 ++++++++++++++++++ 4 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video-app.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 6eba9923e64..7f4658d442c 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -449,8 +449,9 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ var chunks = strings.Split(strings.Trim(bfReqs[i].Request.App.Bundle, "_"), ".") if len(chunks) > 1 { - bfReqs[i].Request.App.Domain = - fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) + appCopy := *bfReqs[i].Request.App + appCopy.Domain = fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) + bfReqs[i].Request.App = &appCopy } } } diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json new file mode 100644 index 00000000000..0734a212c61 --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "com.domain.some" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "com.domain.some", + "domain": "domain.com" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json new file mode 100644 index 00000000000..0e3a32d5437 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "id1234567890" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "id1234567890" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json new file mode 100644 index 00000000000..8327426ca0f --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "some.domain.us/some/page.html", + "domain": "domain.some" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} From a68f7e249c00d0b6e810c6cfbb2fab011d744cb3 Mon Sep 17 00:00:00 2001 From: Eddy Pechuzal <46331062+epechuzal@users.noreply.github.com> Date: Thu, 22 Jul 2021 12:49:36 -0700 Subject: [PATCH 466/603] Sharethrough: Add support for GPID (#1925) --- adapters/sharethrough/butler.go | 10 ++++++++++ adapters/sharethrough/butler_test.go | 4 +++- openrtb_ext/imp_sharethrough.go | 15 ++++++++++----- static/bidder-params/sharethrough.json | 10 ++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index b34ae0844ab..b7dd5003e6a 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -21,6 +21,7 @@ const defaultTmax = 10000 // 10 sec type StrAdSeverParams struct { Pkey string BidID string + GPID string ConsentRequired bool ConsentString string USPrivacySignal string @@ -97,6 +98,11 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open return nil, err } + var gpid string + if strImpParams.Data != nil && strImpParams.Data.PBAdSlot != "" { + gpid = strImpParams.Data.PBAdSlot + } + usPolicySignal := "" if usPolicy, err := ccpa.ReadFromRequest(request); err == nil { usPolicySignal = usPolicy.Consent @@ -107,6 +113,7 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open Uri: s.UriHelper.buildUri(StrAdSeverParams{ Pkey: pKey, BidID: imp.ID, + GPID: gpid, ConsentRequired: s.Util.gdprApplies(request), ConsentString: userInfo.Consent, USPrivacySignal: usPolicySignal, @@ -191,6 +198,9 @@ func (h StrUriHelper) buildUri(params StrAdSeverParams) string { v := url.Values{} v.Set("placement_key", params.Pkey) v.Set("bidId", params.BidID) + if params.GPID != "" { + v.Set("gpid", params.GPID) + } v.Set("consent_required", fmt.Sprintf("%t", params.ConsentRequired)) v.Set("consent_string", params.ConsentString) if params.USPrivacySignal != "" { diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index fbef417e530..3d19eea9171 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -83,7 +83,7 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { "Generates the correct AdServer request from Imp (no user provided)": { inputImp: openrtb2.Imp{ ID: "abc", - Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0} }`), + Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0, "data": { "pbadslot": "adslot" } } }`), Banner: &openrtb2.Banner{ Format: []openrtb2.Format{{H: 30, W: 40}}, }, @@ -435,6 +435,7 @@ func TestBuildUri(t *testing.T) { inputParams: StrAdSeverParams{ Pkey: "pkey", BidID: "bid", + GPID: "gpid", ConsentRequired: true, ConsentString: "consent", USPrivacySignal: "ccpa", @@ -449,6 +450,7 @@ func TestBuildUri(t *testing.T) { "http://abc.com?", "placement_key=pkey", "bidId=bid", + "gpid=gpid", "consent_required=true", "consent_string=consent", "us_privacy=ccpa", diff --git a/openrtb_ext/imp_sharethrough.go b/openrtb_ext/imp_sharethrough.go index 3f3780334e6..7c3f1f6781d 100644 --- a/openrtb_ext/imp_sharethrough.go +++ b/openrtb_ext/imp_sharethrough.go @@ -1,13 +1,18 @@ package openrtb_ext -type ExtImpSharethrough struct { - Pkey string `json:"pkey"` - Iframe bool `json:"iframe"` - IframeSize []int `json:"iframeSize"` - BidFloor float64 `json:"bidfloor"` +type ExtData struct { + PBAdSlot string `json:"pbadslot"` } // ExtImpSharethrough defines the contract for bidrequest.imp[i].ext.sharethrough +type ExtImpSharethrough struct { + Pkey string `json:"pkey"` + Iframe bool `json:"iframe"` + IframeSize []int `json:"iframeSize"` + BidFloor float64 `json:"bidfloor"` + Data *ExtData `json:"data,omitempty"` +} + type ExtImpSharethroughResponse struct { AdServerRequestID string `json:"adserverRequestId"` BidID string `json:"bidId"` diff --git a/static/bidder-params/sharethrough.json b/static/bidder-params/sharethrough.json index ba6580e2a7b..1fe2949bc8f 100644 --- a/static/bidder-params/sharethrough.json +++ b/static/bidder-params/sharethrough.json @@ -26,6 +26,16 @@ "bidfloor": { "type": "number", "description": "The floor price, or minimum amount, a publisher will accept for an impression, given in CPM in USD" + }, + "data": { + "type": "object", + "description": "Ad-specific first party data", + "properties": { + "pbadslot": { + "type": "string", + "description": "Prebid Ad Slot, see: https://docs.prebid.org/features/pbAdSlot.html" + } + } } }, "required": ["pkey"] From cf8b2ffd651828a36a88d311d22e9ecf9e68de83 Mon Sep 17 00:00:00 2001 From: avolokha <84977155+avolokha@users.noreply.github.com> Date: Tue, 27 Jul 2021 20:37:20 +0300 Subject: [PATCH 467/603] Admixer: Fix for bid floor issue#1787 (#1872) --- adapters/admixer/admixer.go | 7 +- .../exemplary/optional-params.json | 133 ++++++++++++++++++ adapters/admixer/params_test.go | 4 + static/bidder-params/admixer.json | 4 +- 4 files changed, 144 insertions(+), 4 deletions(-) diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index ec49950a17e..5008b0ce5c6 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -100,14 +100,17 @@ func preprocess(imp *openrtb2.Imp) error { } //don't use regexp due to possible performance reduce - if len(admixerExt.ZoneId) != 36 { + if len(admixerExt.ZoneId) < 32 || len(admixerExt.ZoneId) > 36 { return &errortypes.BadInput{ Message: "ZoneId must be UUID/GUID", } } imp.TagID = admixerExt.ZoneId - imp.BidFloor = admixerExt.CustomBidFloor + + if imp.BidFloor == 0 && admixerExt.CustomBidFloor > 0 { + imp.BidFloor = admixerExt.CustomBidFloor + } imp.Ext = nil diff --git a/adapters/admixer/admixertest/exemplary/optional-params.json b/adapters/admixer/admixertest/exemplary/optional-params.json index 8ef112bbdb5..b93aa9c8154 100644 --- a/adapters/admixer/admixertest/exemplary/optional-params.json +++ b/adapters/admixer/admixertest/exemplary/optional-params.json @@ -44,6 +44,76 @@ } } } + }, + { + "id": "test-imp-id", + "bidfloor": 0.5, + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } + }, + { + "id": "test-imp-id", + "bidfloor": 0.5, + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + }, + "customFloor": 0.9 + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "customFloor": 0.9, + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } } ] }, @@ -92,6 +162,69 @@ ] } } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.5, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.5, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.9, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } } ] } diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index 71cccb6a3da..11f3feb0657 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -44,6 +44,8 @@ var validParams = []string{ `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": 0.1}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": {"foo": "bar"}}`, `{"zone": "9ff668a2-4122-462e-aaf8-36ea3a54ba21", "customFloor": 0.1, "customParams": {"foo": ["bar", "baz"]}}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA21"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA212"}`, } var invalidParams = []string{ @@ -54,4 +56,6 @@ var invalidParams = []string{ `{"zone": "123", "customFloor": "0.1"}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": -0.1}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": "foo: bar"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA2"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA2112336"}`, } diff --git a/static/bidder-params/admixer.json b/static/bidder-params/admixer.json index 886e33ff2bb..78671931561 100644 --- a/static/bidder-params/admixer.json +++ b/static/bidder-params/admixer.json @@ -8,7 +8,7 @@ "zone": { "type": "string", "description": "Zone ID.", - "pattern": "^([a-fA-F\\d\\-]{36})$" + "pattern": "^([a-fA-F\\d\\-]{32,36})$" }, "customFloor": { "type": "number", @@ -22,4 +22,4 @@ }, "required": ["zone"] -} +} \ No newline at end of file From a3e4d4827ac0493e356c8c87ec6c92cbd2862560 Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Wed, 28 Jul 2021 07:48:52 -0700 Subject: [PATCH 468/603] InMobi: adding native support (#1928) --- adapters/inmobi/inmobi.go | 3 + .../exemplary/simple-app-native.json | 105 ++++++++++++++++++ static/bidder-info/inmobi.yaml | 1 + 3 files changed, 109 insertions(+) create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-app-native.json diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index a23472e8892..63baa8a4ba5 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -124,6 +124,9 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { if imp.Video != nil { mediaType = openrtb_ext.BidTypeVideo } + if imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } break } } diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json new file mode 100644 index 00000000000..3a5bfd38412 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1618933962959" + } + }, + "native": { + "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}" + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1618933962959" + } + }, + "native": { + "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}" + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "native-json", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "native-json", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "native" + }] + }] +} diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 634c03481de..d62a2c9239d 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -6,6 +6,7 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner From c01c993fb5f0c86475f9650fbea6369a7cb3780e Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Thu, 29 Jul 2021 16:47:37 +0200 Subject: [PATCH 469/603] Tappx: new bidder params (#1931) Co-authored-by: Albert Grandes --- adapters/tappx/params_test.go | 15 ++ adapters/tappx/tappx.go | 31 ++++- adapters/tappx/tappx_test.go | 2 +- .../single-banner-impression-extra.json | 130 ++++++++++++++++++ ...ngle-banner-impression-future-feature.json | 9 +- .../exemplary/single-banner-impression.json | 7 +- .../exemplary/single-banner-site.json | 7 +- .../exemplary/single-video-impression.json | 7 +- .../exemplary/single-video-site.json | 7 +- .../tappxtest/supplemental/204status.json | 7 +- .../tappxtest/supplemental/bidfloor.json | 7 +- .../supplemental/http-err-status.json | 7 +- .../supplemental/http-err-status2.json | 7 +- openrtb_ext/imp_tappx.go | 11 +- static/bidder-params/tappx.json | 18 +++ 15 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index 3a73d4dab53..9457924875f 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -35,6 +35,11 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com"}`, `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "mktag":"txmk-xxxxx-xxx-xxxx"}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245", "321"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123", "654"], "bcrid":["245", "321"]}`, } var invalidParams = []string{ @@ -60,4 +65,14 @@ var invalidParams = []string{ `{"tappxkey": 1, "endpoint":"ZZ1INTERNALTEST149147915", "host":""}`, `{"tappxkey":"pub-12345-android-9876", "endpoint": 1, "host":""}`, `{"tappxkey": 1, "endpoint": 1, "host": 123}`, + `{"tappxkey": "1", "endpoint": 1}`, + `{"tappxkey": "1", "endpoint": "ZZ1INTERNALTEST149147915", "host":[]]}`, + `{"tappxkey": "1", "endpoint": 1, "host":"host"}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":1}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":[1,2]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":""}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":"123", bcrid: ["123"]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: 123}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: [123]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":[123], bcrid: ["123"]}`, } diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 5970ccb6cfe..5f0710cf08a 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -18,13 +18,24 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const TAPPX_BIDDER_VERSION = "1.2" +const TAPPX_BIDDER_VERSION = "1.3" const TYPE_CNN = "prebid" type TappxAdapter struct { endpointTemplate template.Template } +type Bidder struct { + Tappxkey string `json:"tappxkey"` + Mktag string `json:"mktag,omitempty"` + Bcid []string `json:"bcid,omitempty"` + Bcrid []string `json:"bcrid,omitempty"` +} + +type Ext struct { + Bidder `json:"bidder"` +} + // Builder builds a new instance of the Tappx adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { template, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -51,7 +62,6 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt Message: "Error parsing bidderExt object", }} } - var tappxExt openrtb_ext.ExtImpTappx if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil { return nil, []error{&errortypes.BadInput{ @@ -59,6 +69,23 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt }} } + ext := Ext{ + Bidder: Bidder{ + Tappxkey: tappxExt.TappxKey, + Mktag: tappxExt.Mktag, + Bcid: tappxExt.Bcid, + Bcrid: tappxExt.Bcrid, + }, + } + + if jsonext, err := json.Marshal(ext); err == nil { + request.Ext = jsonext + } else { + return nil, []error{&errortypes.FailedToRequestBids{ + Message: "Error marshaling tappxExt parameters", + }} + } + var test int test = int(request.Test) diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 10e57d12132..ea7011a7bdc 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url) + match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.3`, url) if err != nil { t.Errorf("Error while running regex validation: %s", err.Error()) return diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json new file mode 100644 index 00000000000..a6ddf2848e2 --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json index 3c3037afefb..259d51cb34f 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -48,7 +48,7 @@ "bidder": { "tappxkey": "pub-12345-android-9876", "endpoint": "ZZ123456PS", - "host": "ZZ123456PS.ssp.tappx.com/rtb/" + "host": "ZZ123456PS.ssp.tappx.com/rtb/" } } } @@ -62,6 +62,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index 54f472d9fff..532e2b1f4a1 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -62,6 +62,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json index 58490233ede..e8858bd6ea6 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -37,7 +37,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -70,6 +70,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-site-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index d6ce0554c5f..23e079258e7 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -67,6 +67,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json index f151151e776..85872b6a29e 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -75,6 +75,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-site-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 1c72cc90f24..918b278e6dc 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 093f77adfc6..3d3ced65e25 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -65,6 +65,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index a80a5eaa675..f1783b3f77a 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index 41dcc26d653..4b855c57404 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/openrtb_ext/imp_tappx.go b/openrtb_ext/imp_tappx.go index c1ca77bf632..ca8693abd9f 100644 --- a/openrtb_ext/imp_tappx.go +++ b/openrtb_ext/imp_tappx.go @@ -1,8 +1,11 @@ package openrtb_ext type ExtImpTappx struct { - Host string `json:"host"` - TappxKey string `json:"tappxkey"` - Endpoint string `json:"endpoint"` - BidFloor float64 `json:"bidfloor,omitempty"` + Host string `json:"host"` + TappxKey string `json:"tappxkey"` + Endpoint string `json:"endpoint"` + BidFloor float64 `json:"bidfloor,omitempty"` + Mktag string `json:"mktag,omitempty"` + Bcid []string `json:"bcid,omitempty"` + Bcrid []string `json:"bcrid,omitempty"` } diff --git a/static/bidder-params/tappx.json b/static/bidder-params/tappx.json index f8feb1913e9..1cf101a44f5 100644 --- a/static/bidder-params/tappx.json +++ b/static/bidder-params/tappx.json @@ -12,6 +12,10 @@ "type": "string", "description": "An ID which identifies the adunit" }, + "mktag": { + "type": "string", + "description": "Minimum bid for this impression expressed in CPM (USD)" + }, "endpoint": { "type": "string", "description": "Endpoint provided to publisher" @@ -19,6 +23,20 @@ "bidfloor": { "type": "number", "description": "Minimum bid for this impression expressed in CPM (USD)" + }, + "bcid": { + "type": "array", + "description": "Block list of CID", + "items": { + "type": "string" + } + }, + "bcrid": { + "type": "array", + "description": "Block list of CRID", + "items": { + "type": "string" + } } }, "required": ["host","tappxkey","endpoint"] From 4a782647b842c1023fbcbe7f348c4b689a99fcc8 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 29 Jul 2021 11:00:46 -0400 Subject: [PATCH 470/603] Fix CVE-2020-35381 (#1942) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7d10a5c9bca..6193e08c47c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible - github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 + github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 diff --git a/go.sum b/go.sum index df2bfb9b459..a4cab742c34 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +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/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= From 2e9b8971803df098d5ecaf702c24a1150bd3aca8 Mon Sep 17 00:00:00 2001 From: el-chuck Date: Thu, 29 Jul 2021 17:42:26 +0200 Subject: [PATCH 471/603] Smaato: Split multiple media types (#1930) Co-authored-by: Bernhard Pickenbrock --- adapters/smaato/smaato.go | 49 ++- .../exemplary/multiple-impressions.json | 8 +- .../exemplary/multiple-media-types.json | 348 ++++++++++++++++++ .../exemplary/simple-banner-app.json | 2 +- .../simple-banner-richMedia-app.json | 2 +- .../exemplary/simple-banner-richMedia.json | 2 +- .../smaatotest/exemplary/simple-banner.json | 4 +- .../smaatotest/exemplary/video-app.json | 2 +- .../smaato/smaatotest/exemplary/video.json | 4 +- .../supplemental/adtype-header-response.json | 2 +- .../supplemental/bad-adm-response.json | 2 +- .../bad-adtype-header-response.json | 2 +- .../bad-expires-header-response.json | 2 +- .../bad-status-code-response.json | 2 +- .../supplemental/banner-w-and-h.json | 2 +- .../supplemental/expires-header-response.json | 2 +- .../supplemental/no-bid-response.json | 2 +- .../supplemental/no-consent-info-request.json | 2 +- .../outdated-expires-header-response.json | 2 +- .../smaatotest/video/multiple-adpods.json | 4 +- .../smaato/smaatotest/video/single-adpod.json | 6 +- .../videosupplemental/bad-adm-response.json | 2 +- .../bad-bid-ext-response.json | 2 +- 23 files changed, 422 insertions(+), 33 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-media-types.json diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index c50efffc994..c84dd356a59 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -17,7 +17,7 @@ import ( "github.com/prebid/prebid-server/util/timeutil" ) -const clientVersion = "prebid_server_0.3" +const clientVersion = "prebid_server_0.4" type adMarkupType string @@ -160,24 +160,53 @@ func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([] errors := make([]error, 0, len(imps)) for _, imp := range imps { - request.Imp = []openrtb2.Imp{imp} - if err := prepareIndividualRequest(request); err != nil { - errors = append(errors, err) - continue - } - - requestData, err := adapter.makeRequest(request) + impsByMediaType, err := splitImpressionsByMediaType(&imp) if err != nil { errors = append(errors, err) continue } - requests = append(requests, requestData) + for _, impByMediaType := range impsByMediaType { + request.Imp = []openrtb2.Imp{impByMediaType} + if err := prepareIndividualRequest(request); err != nil { + errors = append(errors, err) + continue + } + + requestData, err := adapter.makeRequest(request) + if err != nil { + errors = append(errors, err) + continue + } + + requests = append(requests, requestData) + } } return requests, errors } +func splitImpressionsByMediaType(imp *openrtb2.Imp) ([]openrtb2.Imp, error) { + if imp.Banner == nil && imp.Video == nil { + return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."} + } + + imps := make([]openrtb2.Imp, 0, 2) + + if imp.Banner != nil { + impCopy := *imp + impCopy.Video = nil + imps = append(imps, impCopy) + } + + if imp.Video != nil { + imp.Banner = nil + imps = append(imps, *imp) + } + + return imps, nil +} + func (adapter *adapter) makePodRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { pods, orderedKeys, errors := groupImpressionsByPod(request.Imp) requests := make([]*adapters.RequestData, 0, len(pods)) @@ -436,7 +465,7 @@ func setImpForAdspace(imp *openrtb2.Imp) error { return nil } - return &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."} + return nil } func setImpForAdBreak(imps []openrtb2.Imp) error { diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json index e86fea8eb04..c30a9a6a39e 100644 --- a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json +++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json @@ -31,6 +31,7 @@ } ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -65,6 +66,7 @@ "rewarded": 0 } }, + "bidfloor": 0.00456, "ext": { "bidder": { "publisherId": "1100042526", @@ -114,6 +116,7 @@ { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "tagid": "130563103", + "bidfloor": 0.00123, "banner": { "h": 50, "w": 320, @@ -159,7 +162,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, @@ -208,6 +211,7 @@ { "id": "postbid_iframe", "tagid": "130563104", + "bidfloor": 0.00456, "video": { "w": 1024, "h": 768, @@ -264,7 +268,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/multiple-media-types.json b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json new file mode 100644 index 00000000000..a7d97666778 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json @@ -0,0 +1,348 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "bidfloor": 0.00123, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "bidfloor": 0.00123, + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "bidfloor": 0.00123, + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json index 2752fa6e6c7..cd29d93a34d 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json @@ -160,7 +160,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json index bc3a3c28c87..8ddc9a7273f 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json @@ -164,7 +164,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 7f81d39cd81..f0fe35ff206 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -129,7 +129,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index f83e347a684..babce4f892d 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -31,6 +31,7 @@ } ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -80,6 +81,7 @@ { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "tagid": "130563103", + "bidfloor": 0.00123, "banner": { "h": 50, "w": 320, @@ -125,7 +127,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json index 317003f52b3..fc94c2d43cb 100644 --- a/adapters/smaato/smaatotest/exemplary/video-app.json +++ b/adapters/smaato/smaatotest/exemplary/video-app.json @@ -165,7 +165,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index 85699129180..205c02ad84c 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -29,6 +29,7 @@ "rewarded": 0 } }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -82,6 +83,7 @@ { "id": "postbid_iframe", "tagid": "130563103", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -122,7 +124,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json index 59302b6de59..14b966f9bdd 100644 --- a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index 885c077e624..efeba9ed6ae 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json index cfb23bbef85..b066dc1b6cb 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json index 8e41524493d..065b639509e 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json index a087594325e..bfe3dbe2a2d 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json index 9e57c380d27..1d59e96d634 100644 --- a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json +++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json @@ -107,7 +107,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json index 90a78e626cf..be057419177 100644 --- a/adapters/smaato/smaatotest/supplemental/expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/no-bid-response.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json index c8821d2d944..4f674f2a34d 100644 --- a/adapters/smaato/smaatotest/supplemental/no-bid-response.json +++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json index 72f8a2e3b9d..b33fea6e7e1 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json index c65e0824338..abab70facbd 100644 --- a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json index c909dc15f25..e5023d87b28 100644 --- a/adapters/smaato/smaatotest/video/multiple-adpods.json +++ b/adapters/smaato/smaatotest/video/multiple-adpods.json @@ -234,7 +234,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, @@ -386,7 +386,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json index b5bc09d495c..a5f0c0590f5 100644 --- a/adapters/smaato/smaatotest/video/single-adpod.json +++ b/adapters/smaato/smaatotest/video/single-adpod.json @@ -24,6 +24,7 @@ 7 ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -53,6 +54,7 @@ 7 ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -101,6 +103,7 @@ { "id": "1_1", "tagid": "400000001", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -129,6 +132,7 @@ { "id": "1_2", "tagid": "400000001", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -176,7 +180,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json index b13906ce066..08803d1894e 100644 --- a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json @@ -176,7 +176,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json index 2e0556ff15e..75e288362c0 100644 --- a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json +++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json @@ -176,7 +176,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, From 88589d5c39baf1ed41bf22abd86f5ea341ae992c Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 29 Jul 2021 19:00:17 +0200 Subject: [PATCH 472/603] New adapter: Adagio (#1907) --- adapters/adagio/adagio.go | 139 +++++++++++ adapters/adagio/adagio_test.go | 75 ++++++ .../adagiotest/exemplary/banner-web.json | 155 ++++++++++++ .../adagiotest/exemplary/multi-format.json | 165 +++++++++++++ .../adagiotest/exemplary/multi-imp.json | 222 ++++++++++++++++++ .../adagiotest/exemplary/native-web.json | 151 ++++++++++++ .../adagiotest/exemplary/video-web.json | 175 ++++++++++++++ .../adagio/adagiotest/params/race/banner.json | 5 + .../adagio/adagiotest/params/race/native.json | 5 + .../adagio/adagiotest/params/race/video.json | 5 + .../response-miss-ext-bid-type.json | 130 ++++++++++ .../adagiotest/supplemental/status-204.json | 62 +++++ .../adagiotest/supplemental/status-400.json | 67 ++++++ .../adagiotest/supplemental/status-401.json | 67 ++++++ .../adagiotest/supplemental/status-403.json | 67 ++++++ .../adagiotest/supplemental/status-500.json | 68 ++++++ .../adagiotest/supplemental/status-503.json | 67 ++++++ .../adagiotest/supplemental/status-504.json | 67 ++++++ adapters/adagio/params_test.go | 57 +++++ adapters/adagio/usersync.go | 12 + adapters/adagio/usersync_test.go | 34 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/adagio.yaml | 12 + static/bidder-params/adagio.json | 94 ++++++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 28 files changed, 1910 insertions(+) create mode 100644 adapters/adagio/adagio.go create mode 100644 adapters/adagio/adagio_test.go create mode 100644 adapters/adagio/adagiotest/exemplary/banner-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-format.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-imp.json create mode 100644 adapters/adagio/adagiotest/exemplary/native-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/video-web.json create mode 100644 adapters/adagio/adagiotest/params/race/banner.json create mode 100644 adapters/adagio/adagiotest/params/race/native.json create mode 100644 adapters/adagio/adagiotest/params/race/video.json create mode 100644 adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-204.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-400.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-401.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-403.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-500.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-503.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-504.json create mode 100644 adapters/adagio/params_test.go create mode 100644 adapters/adagio/usersync.go create mode 100644 adapters/adagio/usersync_test.go create mode 100644 static/bidder-info/adagio.yaml create mode 100644 static/bidder-params/adagio.json diff --git a/adapters/adagio/adagio.go b/adapters/adagio/adagio.go new file mode 100644 index 00000000000..0da4d6ac9e4 --- /dev/null +++ b/adapters/adagio/adagio.go @@ -0,0 +1,139 @@ +package adagio + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +// Builder builds a new instance of the Adagio adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +type adapter struct { + endpoint string +} + +type extBid struct { + Prebid *openrtb_ext.ExtBidPrebid +} + +// MakeRequests prepares the HTTP requests which should be made to fetch bids. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + json, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Device != nil { + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + if request.Test == 0 { + // Gzip the body + // Note: Gzipping could be handled natively later: https://github.com/prebid/prebid-server/issues/1812 + var bodyBuf bytes.Buffer + gz := gzip.NewWriter(&bodyBuf) + if _, err = gz.Write(json); err == nil { + if err = gz.Close(); err == nil { + json = bodyBuf.Bytes() + headers.Add("Content-Encoding", "gzip") + // /!\ Go already sets the `Accept-Encoding: gzip` header. Never add it manually, or Go won't decompress the response. + //headers.Add("Accept-Encoding", "gzip") + } + } + } + + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: json, + Headers: headers, + } + + return []*adapters.RequestData{requestToBidder}, nil +} + +const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info" + +// MakeBids unpacks the server's response into Bids. +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + switch response.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, nil + case http.StatusServiceUnavailable: + fallthrough + case http.StatusBadRequest: + fallthrough + case http.StatusUnauthorized: + fallthrough + case http.StatusForbidden: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidsCapacity := len(internalRequest.Imp) + errs := make([]error, 0, bidsCapacity) + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + var typedBid *adapters.TypedBid + for _, seatBid := range openRTBBidderResponse.SeatBid { + for _, bid := range seatBid.Bid { + activeBid := bid + + activeExt := &extBid{} + if err := json.Unmarshal(activeBid.Ext, activeExt); err != nil { + errs = append(errs, err) + } + + var bidType openrtb_ext.BidType + if activeExt.Prebid != nil && activeExt.Prebid.Type != "" { + bidType = activeExt.Prebid.Type + } else { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find native/banner/video mediaType \"%s\" ", activeBid.ImpID), + } + errs = append(errs, err) + continue + } + + typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } + } + + return bidderResponse, nil +} diff --git a/adapters/adagio/adagio_test.go b/adapters/adagio/adagio_test.go new file mode 100644 index 00000000000..d5e25c7836d --- /dev/null +++ b/adapters/adagio/adagio_test.go @@ -0,0 +1,75 @@ +package adagio + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func buildFakeBidRequest() openrtb2.BidRequest { + imp1 := openrtb2.Imp{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{}, + Ext: json.RawMessage(`{"bidder": {"organizationId": "1000", "site": "site-name", "placement": "ban_atf"}}`), + } + + fakeBidRequest := openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{imp1}, + } + + return fakeBidRequest +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adagiotest", bidder) +} + +func TestMakeRequests_NoGzip(t *testing.T) { + fakeBidRequest := buildFakeBidRequest() + fakeBidRequest.Test = 1 // Do not use Gzip in Test Mode. + + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil) + + assert.Nil(t, errs) + assert.Equal(t, 1, len(requestData)) + + body := &openrtb2.BidRequest{} + err := json.Unmarshal(requestData[0].Body, body) + assert.NoError(t, err, "Request body unmarshalling error should be nil") + assert.Equal(t, 1, len(body.Imp)) +} + +func TestMakeRequests_Gzip(t *testing.T) { + fakeBidRequest := buildFakeBidRequest() + + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil) + assert.Empty(t, errs, "Got errors while making requests") + assert.Equal(t, []string{"gzip"}, requestData[0].Headers["Content-Encoding"]) +} diff --git a/adapters/adagio/adagiotest/exemplary/banner-web.json b/adapters/adagio/adagiotest/exemplary/banner-web.json new file mode 100644 index 00000000000..732b40d2c1d --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/banner-web.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/multi-format.json b/adapters/adagio/adagiotest/exemplary/multi-format.json new file mode 100644 index 00000000000..85e0be26131 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/multi-format.json @@ -0,0 +1,165 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "multi-format-id", + "banner": { + "w":320, + "h":50 + }, + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "multi-format-id", + "banner": { + "w":320, + "h":50 + }, + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "multi-format-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "multi-format-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/multi-imp.json b/adapters/adagio/adagiotest/exemplary/multi-imp.json new file mode 100644 index 00000000000..66af28ea559 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/multi-imp.json @@ -0,0 +1,222 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "banner-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + }, + { + "id": "video-impression-id", + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "banner-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + }, + { + "id": "video-impression-id", + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "banner-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + { + "id": "b4ae1b4e2fc24a4fb45540082e98e162", + "impid": "video-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "banner-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }, + { + "bid": { + "id": "b4ae1b4e2fc24a4fb45540082e98e162", + "impid": "video-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/native-web.json b/adapters/adagio/adagiotest/exemplary/native-web.json new file mode 100644 index 00000000000..0085aaddc64 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/native-web.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "acuityads": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/video-web.json b/adapters/adagio/adagiotest/exemplary/video-web.json new file mode 100644 index 00000000000..353420ee962 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/video-web.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video\/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video\/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "adagio": { + "outstream": { + "bvwUrl": "https://cool.io" + } + }, + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "adagio": { + "outstream": { + "bvwUrl": "https://cool.io" + } + }, + "prebid": { + "type": "video" + } + } + }, + "type":"video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/params/race/banner.json b/adapters/adagio/adagiotest/params/race/banner.json new file mode 100644 index 00000000000..66694466d84 --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" +} diff --git a/adapters/adagio/adagiotest/params/race/native.json b/adapters/adagio/adagiotest/params/race/native.json new file mode 100644 index 00000000000..4affd5b232c --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/native.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" +} diff --git a/adapters/adagio/adagiotest/params/race/video.json b/adapters/adagio/adagiotest/params/race/video.json new file mode 100644 index 00000000000..4affd5b232c --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" +} diff --git a/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json new file mode 100644 index 00000000000..7b1267f6d50 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": {} + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-204.json b/adapters/adagio/adagiotest/supplemental/status-204.json new file mode 100644 index 00000000000..4d604a01fb9 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-204.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-400.json b/adapters/adagio/adagiotest/supplemental/status-400.json new file mode 100644 index 00000000000..093c5458c0a --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": "bad request" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-401.json b/adapters/adagio/adagiotest/supplemental/status-401.json new file mode 100644 index 00000000000..a33aca203d0 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-401.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 401, + "body": "unauthorized" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 401. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-403.json b/adapters/adagio/adagiotest/supplemental/status-403.json new file mode 100644 index 00000000000..59c5a9cbf6b --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-403.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 403, + "body": "forbidden" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-500.json b/adapters/adagio/adagiotest/supplemental/status-500.json new file mode 100644 index 00000000000..0077d7457f9 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-500.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "test": 1, + "debug": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": "internal error" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-503.json b/adapters/adagio/adagiotest/supplemental/status-503.json new file mode 100644 index 00000000000..d2a52893de5 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-503.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 503, + "body": "Service unavailable" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-504.json b/adapters/adagio/adagiotest/supplemental/status-504.json new file mode 100644 index 00000000000..1b779d5c83f --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-504.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 504, + "body": "gateway timeout" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 504. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/params_test.go b/adapters/adagio/params_test.go new file mode 100644 index 00000000000..ee8f702e451 --- /dev/null +++ b/adapters/adagio/params_test.go @@ -0,0 +1,57 @@ +package adagio + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf" }`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "_unknown": "ban_atf"}`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "b": "b"}}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdagio, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Adagio params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "organizationId": "", "site": "", "placement": "" }`, + `{ "organizationId": "", "site": "2", "placement": "3" }`, + `{ "organizationId": "1", "site": "", "placement": "3" }`, + `{ "organizationId": "1", "site": "2", "placement": "" }`, + `{ "organizationId": 1, "site": "2", "placement": "3" }`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "notastring": true}}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdagio, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/adagio/usersync.go b/adapters/adagio/usersync.go new file mode 100644 index 00000000000..a7230feaada --- /dev/null +++ b/adapters/adagio/usersync.go @@ -0,0 +1,12 @@ +package adagio + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdagioSyncer(tmpl *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adagio", tmpl, adapters.SyncTypeRedirect) +} diff --git a/adapters/adagio/usersync_test.go b/adapters/adagio/usersync_test.go new file mode 100644 index 00000000000..cd4195e16df --- /dev/null +++ b/adapters/adagio/usersync_test.go @@ -0,0 +1,34 @@ +package adagio + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdagioSyncer(t *testing.T) { + syncURL := "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdagioSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "ANDFJDS", + }, + CCPA: ccpa.Policy{ + Consent: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://mp.4dex.io/sync?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index b8ff3bd6116..6e8b661a13b 100644 --- a/config/config.go +++ b/config/config.go @@ -602,6 +602,7 @@ func (cfg *Configuration) setDerivedDefaults() { externalURL := cfg.ExternalURL setDefaultUsersync(cfg.Adapters, openrtb_ext.Bidder33Across, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAcuityAds, "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdagio, "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%7B%7BUID%7D%7D%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdf, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdform, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderAdgeneration doesn't have a good default. @@ -849,6 +850,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") + v.SetDefault("adapters.adagio.endpoint", "https://mp.4dex.io/ortb2") v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb") v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1d91fadd96d..e1dbc3509b2 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -4,6 +4,7 @@ import ( "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adagio" "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adgeneration" @@ -129,6 +130,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { return map[openrtb_ext.BidderName]adapters.Builder{ openrtb_ext.Bidder33Across: ttx.Builder, openrtb_ext.BidderAcuityAds: acuityads.Builder, + openrtb_ext.BidderAdagio: adagio.Builder, openrtb_ext.BidderAdf: adf.Builder, openrtb_ext.BidderAdform: adform.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8a589bd1a74..2a08fd19eb2 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -75,6 +75,7 @@ func IsBidderNameReserved(name string) bool { const ( Bidder33Across BidderName = "33across" BidderAcuityAds BidderName = "acuityads" + BidderAdagio BidderName = "adagio" BidderAdf BidderName = "adf" BidderAdform BidderName = "adform" BidderAdgeneration BidderName = "adgeneration" @@ -200,6 +201,7 @@ func CoreBidderNames() []BidderName { return []BidderName{ Bidder33Across, BidderAcuityAds, + BidderAdagio, BidderAdf, BidderAdform, BidderAdgeneration, diff --git a/static/bidder-info/adagio.yaml b/static/bidder-info/adagio.yaml new file mode 100644 index 00000000000..3661191b3a1 --- /dev/null +++ b/static/bidder-info/adagio.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "dev@adagio.io" +gvlVendorID: 617 +modifyingVastXmlAllowed: false +debug: + allow: true +capabilities: + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/adagio.json b/static/bidder-params/adagio.json new file mode 100644 index 00000000000..955c58c73ec --- /dev/null +++ b/static/bidder-params/adagio.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adagio Adapter Params", + "description": "A schema which validates params accepted by the Adagio adapter", + "type": "object", + "required": [ + "organizationId", + "site", + "placement" + ], + "properties": { + "organizationId": { + "type": "string", + "description": "Name to identify the organization", + "minLength": 1 + }, + "site": { + "type": "string", + "description": "Name to identify the site", + "minLength": 1 + }, + "placement": { + "type": "string", + "description": "Name to identify the placement", + "minLength": 1 + }, + "pageviewId": { + "type": "string", + "description": "Name to identify the pageview" + }, + "pagetype": { + "type": "string", + "description": "Name to identify the page type" + }, + "category": { + "type": "string", + "description": "Name to identify the category" + }, + "subcategory": { + "type": "string", + "description": "Name to identify the subcategory" + }, + "environment": { + "type": "string", + "description": "Name to identify the environment" + }, + "features": { + "type": "object", + "patternProperties": { + "^[a-zA-Z_]": { "type": "string" } + } + }, + "prebidVersion:": { + "type": "string", + "description": "Name to identify the version of Prebid.js" + }, + "debug": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "cpm": { + "type": "number" + }, + "lazyLoad": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "threshold": { + "type": "number" + }, + "rootMargin": { + "type": "string" + } + } + } + } + }, + "native": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "plcmttype": { + "type": "number" + } + } + } + } +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 674fc136527..8275869f5b2 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -8,6 +8,7 @@ import ( "github.com/golang/glog" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adagio" "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" @@ -111,6 +112,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.Bidder33Across, ttx.New33AcrossSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAcuityAds, acuityads.NewAcuityAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdagio, adagio.NewAdagioSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdf, adf.NewAdfSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 8a2a3e5d2e1..342d2ae81b9 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -16,6 +16,7 @@ func TestNewSyncerMap(t *testing.T) { Adapters: map[string]config.Adapter{ string(openrtb_ext.Bidder33Across): syncConfig, string(openrtb_ext.BidderAcuityAds): syncConfig, + string(openrtb_ext.BidderAdagio): syncConfig, string(openrtb_ext.BidderAdf): syncConfig, string(openrtb_ext.BidderAdform): syncConfig, string(openrtb_ext.BidderAdkernel): syncConfig, From bab487e8a54b37dca75c5f86694170e60d638ef3 Mon Sep 17 00:00:00 2001 From: Joshua Gross <820727+grossjo@users.noreply.github.com> Date: Thu, 29 Jul 2021 14:12:02 -0400 Subject: [PATCH 473/603] IX: update required site id field to be more flexible (#1934) Co-authored-by: Joshua Gross --- adapters/ix/params_test.go | 59 ++++++++++++++++++++++++++++++++++++ static/bidder-params/ix.json | 18 +++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 adapters/ix/params_test.go diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go new file mode 100644 index 00000000000..9246a43a725 --- /dev/null +++ b/adapters/ix/params_test.go @@ -0,0 +1,59 @@ +package ix + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderIx, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected ix params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderIx, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"siteid":"1234"}`, + `{"siteID":"12345"}`, + `{"siteId":"123456"}`, + `{"siteid":"1234567", "size": [640,480]}`, +} + +var invalidParams = []string{ + `{"siteid":""}`, + `{"siteID":""}`, + `{"siteId":""}`, + `{"siteid":"1234", "siteID":"12345"}`, + `{"siteid":"1234", "siteId":"123456"}`, + `{"siteid":123}`, + `{"siteids":"123"}`, + `{"notaparam":"123"}`, + `{"siteid":"123", "size": [1,2,3]}`, + `null`, + `true`, + `0`, + `abc`, + `[]`, + `{}`, +} diff --git a/static/bidder-params/ix.json b/static/bidder-params/ix.json index 155cfa21892..a7a5cb7308a 100644 --- a/static/bidder-params/ix.json +++ b/static/bidder-params/ix.json @@ -4,10 +4,20 @@ "description": "A schema which validates params accepted by the Ix adapter", "type": "object", "properties": { + "siteid": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the site selling the impression, preferred." + }, "siteId": { "type": "string", "minLength": 1, - "description": "An ID which identifies the site selling the impression" + "description": "An ID which identifies the site selling the impression." + }, + "siteID": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the site selling the impression." }, "size": { "type": "array", @@ -19,5 +29,9 @@ "description": "An array of two integer containing the dimension" } }, - "required": ["siteId"] + "oneOf": [ + {"required": ["siteid"]}, + {"required": ["siteId"]}, + {"required": ["siteID"]} + ] } From a956d699a90919568100b8a39fdd9e1b1f8c631a Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Thu, 16 Jan 2020 10:13:54 -0600 Subject: [PATCH 474/603] Add SmartRTB adapter (#1071) --- docs/bidders/smartrtb.md | 39 +++++++++++++++++++++++++++++ usersync/usersyncers/syncer_test.go | 1 + 2 files changed, 40 insertions(+) create mode 100644 docs/bidders/smartrtb.md diff --git a/docs/bidders/smartrtb.md b/docs/bidders/smartrtb.md new file mode 100644 index 00000000000..ffa88f663e8 --- /dev/null +++ b/docs/bidders/smartrtb.md @@ -0,0 +1,39 @@ +# SmartRTB Bidder + +[SmartRTB](https://smrtb.com/) supports the following parameters to be present in the `ext` object of impression requests: + +- "pub_id" type string - Required. Publisher ID assigned to you. +- "zone_id" type string - Optional. Enables mapping for further settings and reporting in the Marketplace UI. +- "force_bid" type bool - Optional. If zone ID is mapped, this may be set to always return fake sample bids (banner, video) + +Please contact us to create a new Smart RTB Marketplace account, and for any assistance in configuration. +You may email info@smrtb.com for inquiries. + +## Test Request + +This sample request is our global test placement and should always return a branded banner bid. + +``` + { + "id": "abc", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "test", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "smartrtb": { + "pub_id": "test", + "zone_id": "N4zTDq3PPEHBIODv7cXK", + "force_bid": true + } + } + }] + } +``` diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 10a95fb4b67..753da7d4da7 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -83,6 +83,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, + string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTappx): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, From 437b93bc05a24c5915e9812444ae3bfcbdb07a1c Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Mon, 10 Feb 2020 16:38:49 -0500 Subject: [PATCH 475/603] Adds timeout notifications for Facebook (#1182) --- adapters/audienceNetwork/facebook.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 5cbdbc90561..c4c126468c2 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -456,3 +456,20 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (* return &timeoutReq, nil } + +func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { + // Note, facebook creates one request per imp, so all these requests will only have one imp in them + auction_id, err := jsonparser.GetString(req.Body, "imp", "[0]", "id") + if err != nil { + return &adapters.RequestData{}, []error{err} + } + + uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, fa.platformID, auction_id) + timeoutReq := adapters.RequestData{ + Method: "GET", + Uri: uri, + Body: nil, + Headers: http.Header{}, + } + return &timeoutReq, nil +} From 53dce2d39ab2d3d1667bc773830ce9af727c10f9 Mon Sep 17 00:00:00 2001 From: Viacheslav Chimishuk Date: Fri, 21 Feb 2020 20:14:53 +0200 Subject: [PATCH 476/603] Add Adoppler bidder support. (#1186) * Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter. --- .../adopplertest/exemplary/multibid.json | 60 +++++++++++++++++++ .../adopplertest/exemplary/no-bid.json | 13 ++++ 2 files changed, 73 insertions(+) create mode 100644 adapters/adoppler/adopplertest/exemplary/multibid.json create mode 100644 adapters/adoppler/adopplertest/exemplary/no-bid.json diff --git a/adapters/adoppler/adopplertest/exemplary/multibid.json b/adapters/adoppler/adopplertest/exemplary/multibid.json new file mode 100644 index 00000000000..851f4c5b917 --- /dev/null +++ b/adapters/adoppler/adopplertest/exemplary/multibid.json @@ -0,0 +1,60 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}, + {"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}, + {"id": "imp3", + "native": {"request": "{}"}, + "ext": {"bidder": {"adunit": "unit3"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp1-resp1", + "seatbid": [{"bid": [{"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", + "body": {"id": "req1-unit2", + "imp": [{"id": "imp2", + "video": {"minduration": 120, + "mimes": ["video/mp4"]}, + "ext": {"bidder": {"adunit": "unit2"}}}]}}, + "mockResponse": {"status": 200, + "body": {"id": "req1-imp2-resp2", + "seatbid": [{"bid": [{"id": "req1-imp2-bid1", + "impid": "imp2", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": {"ads": {"video": {"duration": 121}}}}]}], + "cur": "USD"}}}, + {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit3", + "body": {"id": "req1-unit3", + "imp": [{"id": "imp3", + "native": {"request": "{}"}, + "ext": {"bidder": {"adunit": "unit3"}}}]}}, + "mockResponse": {"status": 204, + "body": ""}}], + "expectedBidResponses": [{"currency": "USD", + "bids": [{"bid": {"id": "req1-imp1-bid1", + "impid": "imp1", + "price": 0.12, + "adm": "a banner"}, + "type": "banner"}]}, + {"currency": "USD", + "bids": [{"bid": {"id": "req1-imp2-bid1", + "impid": "imp2", + "price": 0.24, + "adm": "", + "cat": ["IAB1", "IAB2"], + "ext": {"ads": {"video": {"duration": 121}}}}, + "type": "video"}]}]} diff --git a/adapters/adoppler/adopplertest/exemplary/no-bid.json b/adapters/adoppler/adopplertest/exemplary/no-bid.json new file mode 100644 index 00000000000..0e0f13586a8 --- /dev/null +++ b/adapters/adoppler/adopplertest/exemplary/no-bid.json @@ -0,0 +1,13 @@ +{"mockBidRequest": {"id": "req1", + "imp":[{"id": "imp1", + "banner": {"w": 100, + "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}, + "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", + "body": {"id": "req1-unit1", + "imp": [{"id": "imp1", + "banner": {"w": 100, "h": 200}, + "ext": {"bidder": {"adunit": "unit1"}}}]}}, + "mockResponse": {"status": 204, + "body": ""}}], + "expectedBidResponses": []} From d54d766e623fa8e8a92f2657f8c5e11214d35177 Mon Sep 17 00:00:00 2001 From: rhaksi-kidoz <61601767+rhaksi-kidoz@users.noreply.github.com> Date: Tue, 17 Mar 2020 22:27:52 -0700 Subject: [PATCH 477/603] Kidoz adapter (#1210) Co-authored-by: Ryan Haksi --- analytics/config/testFiles/test-20200303 | 0 go.mod | 1 + go.sum | 2 ++ 3 files changed, 3 insertions(+) create mode 100644 analytics/config/testFiles/test-20200303 diff --git a/analytics/config/testFiles/test-20200303 b/analytics/config/testFiles/test-20200303 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go.mod b/go.mod index dee3615b79b..6d604adf78a 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 + github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index 0ccf122d248..b40d6326e33 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= +github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= From 972aa8a17d22acf5225ac79aa3289e4917765bc3 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 30 Mar 2020 07:48:41 -0700 Subject: [PATCH 478/603] AMP CCPA Fix (#1187) --- analytics/config/testFiles/test-20200303 | 0 privacy/gdpr/policy_test.go | 29 ++++++++++++++++++++++++ privacy/policies.go | 25 ++++++++++++++++++++ 3 files changed, 54 insertions(+) delete mode 100644 analytics/config/testFiles/test-20200303 diff --git a/analytics/config/testFiles/test-20200303 b/analytics/config/testFiles/test-20200303 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index dc8f56425c5..40828cf17c7 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -34,3 +34,32 @@ func TestValidateConsent(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } + +func TestValidateConsent(t *testing.T) { + testCases := []struct { + description string + consent string + expectError bool + }{ + { + description: "Invalid", + consent: "", + expectError: true, + }, + { + description: "Valid", + consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + expectError: false, + }, + } + + for _, test := range testCases { + result := ValidateConsent(test.consent) + + if test.expectError { + assert.Error(t, result, test.description) + } else { + assert.NoError(t, result, test.description) + } + } +} diff --git a/privacy/policies.go b/privacy/policies.go index bc844a4e463..71f4a8adbb4 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -12,3 +12,28 @@ type Policies struct { GDPR gdpr.Policy LMT lmt.Policy } + +// ReadPoliciesFromConsent inspects the consent string kind and sets the corresponding values in a new Policies object. +func ReadPoliciesFromConsent(consent string) (Policies, bool) { + if len(consent) == 0 { + return Policies{}, false + } + + if err := gdpr.ValidateConsent(consent); err == nil { + return Policies{ + GDPR: gdpr.Policy{ + Consent: consent, + }, + }, true + } + + if err := ccpa.ValidateConsent(consent); err == nil { + return Policies{ + CCPA: ccpa.Policy{ + Value: consent, + }, + }, true + } + + return Policies{}, false +} From 718debcd99b17e0268a9e722f8552af438ccd6f9 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 14 Apr 2020 12:32:07 -0400 Subject: [PATCH 479/603] Add kidoz bidder info (#1257) got this info from email communication with kidoz --- docs/bidders/kidoz.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/bidders/kidoz.md diff --git a/docs/bidders/kidoz.md b/docs/bidders/kidoz.md new file mode 100644 index 00000000000..433dd71c2ca --- /dev/null +++ b/docs/bidders/kidoz.md @@ -0,0 +1,9 @@ +# Kidoz Bidder + +Kidoz is exclusively for Mobile app COPPA compatible ads, 100% kid relevant and appropriate. + +In order for a company to receive bids from Kidoz, they must first open a publisher account at Kidoz.net +(https://accounts.kidoz.net/publishers/register) and accept the Kidoz Terms and Conditions and Privacy Policy. +Kidoz publishers must confirm that all of their content properties are COPPA and GDPR compliant and perform no monitoring +or tracking of U13 users in their operations. New publishers are provided a Publisher ID and AccessToken, this can also +be used to login to their dashboard at the Kidoz.net portal to monitor their account activity. From f5e18d10dbc414f9ad09a16cb18b6de6ca73f74e Mon Sep 17 00:00:00 2001 From: Aadesh Date: Wed, 22 Apr 2020 10:29:25 -0400 Subject: [PATCH 480/603] populate the app ID in the FAN timeout notif url with the publisher ID (#1265) and the auction with the request ID Co-authored-by: Aadesh Patel --- adapters/audienceNetwork/facebook.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index c4c126468c2..9919c50bd7c 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -458,18 +458,39 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (* } func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { - // Note, facebook creates one request per imp, so all these requests will only have one imp in them - auction_id, err := jsonparser.GetString(req.Body, "imp", "[0]", "id") + var ( + rID string + pubID string + err error + ) + + // Note, the facebook adserver can only handle single impression requests, so we have to split multi-imp requests into + // multiple request. In order to ensure that every split request has a unique ID, the split request IDs are set to the + // corresponding imp's ID + rID, err = jsonparser.GetString(req.Body, "id") if err != nil { return &adapters.RequestData{}, []error{err} } - uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, fa.platformID, auction_id) + // The publisher ID is either in the app object or the site object, depending on the supply of the request so we need + // to check both + pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id") + if err != nil { + pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id") + if err != nil { + return &adapters.RequestData{}, []error{ + errors.New("path [app|site].publisher.id not found in the request"), + } + } + } + + uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, pubID, rID) timeoutReq := adapters.RequestData{ Method: "GET", Uri: uri, Body: nil, Headers: http.Header{}, } + return &timeoutReq, nil } From cbdcd7e01732aad4bc2b41acda407f2ae2ae59f4 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 29 Apr 2020 08:15:23 -0700 Subject: [PATCH 481/603] * Add PubMatic bidder doc file (#1255) * Add app video capability to PubMatic bidder info file --- docs/bidders/pubmatic.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/bidders/pubmatic.md diff --git a/docs/bidders/pubmatic.md b/docs/bidders/pubmatic.md new file mode 100644 index 00000000000..610108b2e07 --- /dev/null +++ b/docs/bidders/pubmatic.md @@ -0,0 +1,33 @@ +# PubMatic Bidder + +## Test Request + +The following test parameters can be used to verify that Prebid Server is working properly with the +PubMatic adapter. This example includes an `imp` object with an PubMatic test publisher ID, ad slot, +and sizes that would match with the test creative. + +``` +"imp":[ + { + "id":“"some-impression-id”, + "banner":{ + "format":[ + { + "w":300, + "h":250 + }, + { + "w":300, + "h":600 + } + ] + }, + "ext":{ + "pubmatic":{ + "publisherId":“156276”, + "adSlot":"pubmatic_test" + } + } + } + ] +``` \ No newline at end of file From 3099dd77b7a8303dd00dc9785a57597f881ab4a7 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Tue, 12 May 2020 07:27:03 -0700 Subject: [PATCH 482/603] Added OpenX Bidder adapter documentation (#1291) --- docs/bidders/openx.md | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/bidders/openx.md diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md new file mode 100644 index 00000000000..c366db3ab61 --- /dev/null +++ b/docs/bidders/openx.md @@ -0,0 +1,62 @@ +# OpenX Bidder + +OpenX supports the following parameters: + +| property | type | required? | description | example | +|----------|------|-----------|-------------|---------| +| unit | string | required | The ad unit id | "10092842" | +| delDomain | string | required | The delivery domain for the customer | "sademo-d.openx.net" | +| customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | +| customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | + +If you have any questions regarding setting up, please reach out to your account manager or + + +## Test Request + +### App Impression Object +``` +{ + "id": "test-impression-id", + "banner": { + "format": [ + { + "w": 480, + "h": 300 + }, + { + "w": 480, + "h": 320 + } + ] + }, + "ext": { + "openx": { + "delDomain": "mobile-d.openx.net", + "unit": "541028953" + } + } +} +``` + + +### Web +``` +{ + "id": "div1", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "openx": { + "unit": "540949380", + "delDomain": "sademo-d.openx.net" + }, + } +} +``` \ No newline at end of file From c365b2a065ddcf8fa6ab6a75978beb463d379056 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 21 May 2020 12:33:39 -0400 Subject: [PATCH 483/603] Restore the AMP privacy exception as an option. (#1311) * Restore the AMP privacy exception as an option. * Adds missing test case * More PR feedback * Remove unused constant * Comment tweak --- endpoints/auction_test.go | 4 ++++ endpoints/cookie_sync_test.go | 4 ++++ endpoints/setuid_test.go | 4 ++++ gdpr/impl.go | 8 ++++++++ 4 files changed, 20 insertions(+) diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 17ed7f74f45..af15e8a253a 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -458,6 +458,10 @@ func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder return m.allowPI, m.allowGeo, m.allowID, nil } +func (m *auctionMockPermissions) AMPException() bool { + return false +} + func TestBidSizeValidate(t *testing.T) { bids := make(pbs.PBSBidSlice, 0) // bid1 will be rejected due to undefined size when adunit has multiple sizes diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index d4b89a15118..cbfefe64141 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -257,3 +257,7 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } + +func (g *gdprPerms) AMPException() bool { + return false +} diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 0d68c15bea8..e937eee6b62 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -443,6 +443,10 @@ func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrt return g.allowPI, g.allowPI, g.allowPI, nil } +func (g *mockPermsSetUID) AMPException() bool { + return false +} + func newFakeSyncer(familyName string) usersync.Usersyncer { return fakeSyncer{ familyName: familyName, diff --git a/gdpr/impl.go b/gdpr/impl.go index 55d1cd4aeb0..728ed06c0d1 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -101,6 +101,10 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return SignalYes } +func (p *permissionsImpl) AMPException() bool { + return p.cfg.AMPException +} + func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) { if consent == "" { @@ -283,3 +287,7 @@ func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return false, false, false, nil } + +func (a AlwaysAllow) AMPException() bool { + return false +} From e6a4dae8a00e2a48e649e96c23bc4a1caff23edd Mon Sep 17 00:00:00 2001 From: Mirko Feddern <3244291+mirkorean@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:54:29 +0200 Subject: [PATCH 484/603] Add Yieldlab Adapter (#1287) Co-authored-by: Mirko Feddern Signed-off-by: Alex Klinkert Co-authored-by: Alexander Pinnecke Co-authored-by: Alex Klinkert Co-authored-by: Mirko Feddern --- usersync/usersyncers/syncer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 753da7d4da7..1b62cd73f1c 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -93,6 +93,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, + string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, From c57b8dee0a937cdba0238dc94b274f4119d53129 Mon Sep 17 00:00:00 2001 From: Artur Aleksanyan Date: Tue, 9 Jun 2020 18:48:04 +0400 Subject: [PATCH 485/603] Add Pubnative bidder documentation (#1340) --- docs/bidders/pubnative.md | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/bidders/pubnative.md diff --git a/docs/bidders/pubnative.md b/docs/bidders/pubnative.md new file mode 100644 index 00000000000..a25cafe0cd5 --- /dev/null +++ b/docs/bidders/pubnative.md @@ -0,0 +1,62 @@ +# Pubnative Bidder + +## Prerequisite +Before adding PubNative as a new bidder, there are 3 prerequisites: +- As a Publisher, you need to have Prebid Mobile SDK integrated. +- You need a configured Prebid Server (either self-hosted or hosted by 3rd party). +- You need to be integrated with Ad Server SDK (e.g. Mopub) or internal product which communicates with Prebid Mobile SDK. + +Please see [documentation](https://developers.pubnative.net/docs/prebid-adding-pubnative-as-a-bidder) for more info. + +## Configuration + +- bidder should be always set to "pubnative" (`imp.ext.pubnative`) +- zone_id (int) should be always set to 1, unless special use case agreed with our account manager. (`imp.ext.pubnative.zone_id`) +- app_auth_token (string) is unique per publisher app. Please contact our account manager to obtain yours. (`imp.ext.pubnative.app_auth_token`) + +An example is illustrated in a section below. + +## Testing + +Please consult with our Account Manager for testing. +We need to confirm that your ad request is correctly received by our system. + +The following test parameters can be used to verify that Prebid Server is working properly with the +Pubnative adapter. + +The following json can be used to do a request to prebid server for verifying its integration with Pubnative adapter. + +```json +{ + "id": "some-impression-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "pubnative": { + "zone_id": 1, + "app_auth_token": "b620e282f3c74787beedda34336a4821" + } + } + } + ], + "device": { + "os": "android", + "h": 700, + "w": 375 + }, + "tmax": 500, + "test": 1 +} +``` \ No newline at end of file From 7602bf336cefe7134400e0aafcd2d4496d89128a Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 9 Jun 2020 19:32:38 +0300 Subject: [PATCH 486/603] Add Adtarget server adapter (#1319) * Add Adtarget server adapter * Suggested changes for Adtarget --- docs/bidders/adtarget.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/bidders/adtarget.md diff --git a/docs/bidders/adtarget.md b/docs/bidders/adtarget.md new file mode 100644 index 00000000000..b658a728a2b --- /dev/null +++ b/docs/bidders/adtarget.md @@ -0,0 +1,5 @@ +# Adtarget bidder + +To use the Adtarget bidder you will need an aid from an exchange account on [https://console.adtarget.com.tr](adtarget.com.tr). + +For further information, please contact kamil@adtarget.com.tr \ No newline at end of file From e2e872171b21cffabac01782af9ced9e51117f08 Mon Sep 17 00:00:00 2001 From: Richard Lee <14349+dlackty@users.noreply.github.com> Date: Mon, 15 Jun 2020 23:21:03 +0800 Subject: [PATCH 487/603] Avoid overriding AMP request original size with mutli-size (#1352) --- endpoints/openrtb2/amp_auction_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 079b9adb6d4..7322861efd5 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -857,6 +857,24 @@ func TestSizeWithMultisize(t *testing.T) { }.execute(t) } +func TestSizeWithMultisize(t *testing.T) { + formatOverrideSpec{ + width: 20, + height: 40, + multisize: "200x50,100x60", + expect: []openrtb.Format{{ + W: 20, + H: 40, + }, { + W: 200, + H: 50, + }, { + W: 100, + H: 60, + }}, + }.execute(t) +} + func TestHeightOnly(t *testing.T) { formatOverrideSpec{ height: 200, From 32db017a41b59f70158f754115940271f97cff94 Mon Sep 17 00:00:00 2001 From: Simon Critchley Date: Thu, 18 Jun 2020 15:08:06 +0100 Subject: [PATCH 488/603] Adds Avocet adapter (#1354) --- adapters/avocet/avocet/exemplary/banner.json | 106 +++++++++++++++++++ adapters/avocet/avocet/exemplary/video.json | 104 ++++++++++++++++++ docs/bidders/avocet.md | 5 + 3 files changed, 215 insertions(+) create mode 100644 adapters/avocet/avocet/exemplary/banner.json create mode 100644 adapters/avocet/avocet/exemplary/video.json create mode 100644 docs/bidders/avocet.md diff --git a/adapters/avocet/avocet/exemplary/banner.json b/adapters/avocet/avocet/exemplary/banner.json new file mode 100644 index 00000000000..b5e308ea725 --- /dev/null +++ b/adapters/avocet/avocet/exemplary/banner.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5b51e49634f2021f127ff7c9", + "h": 250, + "id": "bc708396-9202-437b-b726-08b9864cb8b8", + "impid": "test-imp-id", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + "language": "en", + "price": 15.64434783, + "w": 300 + } + ], + "seat": "TEST_SEAT_ID" + } + ] + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "adm": "", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5b51e49634f2021f127ff7c9", + "h": 250, + "id": "bc708396-9202-437b-b726-08b9864cb8b8", + "impid": "test-imp-id", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", + "language": "en", + "price": 15.64434783, + "w": 300 + }, + "type": "banner" + } + ] +} diff --git a/adapters/avocet/avocet/exemplary/video.json b/adapters/avocet/avocet/exemplary/video.json new file mode 100644 index 00000000000..2398256b0dd --- /dev/null +++ b/adapters/avocet/avocet/exemplary/video.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placement": "5ea9601ac865f911007f1b6a" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "bidid": "a0eec3aa-f9f6-42fb-9aa4-f1b5656d4f42", + "id": "749d36d7-c993-455f-aefd-ffd8a7e3ccf", + "seatbid": [ + { + "bid": [ + { + "adm": "Avocet", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5ec530e32d57fe1100f17d87", + "h": 396, + "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + "impid": "dfp-ad--top-above-nav", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + "language": "en", + "price": 15.64434783, + "w": 600, + "ext": { + "avocet": { + "duration": 30 + } + } + } + ], + "seat": "TEST_SEAT_ID" + } + ] + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "adm": "Avocet", + "adomain": ["avocet.io"], + "cid": "5b51e2d689654741306813a4", + "crid": "5ec530e32d57fe1100f17d87", + "h": 396, + "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", + "impid": "dfp-ad--top-above-nav", + "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", + "language": "en", + "price": 15.64434783, + "w": 600, + "ext": { + "avocet": { + "duration": 30 + } + } + }, + "type": "video" + } + ] +} diff --git a/docs/bidders/avocet.md b/docs/bidders/avocet.md new file mode 100644 index 00000000000..6aa67391af4 --- /dev/null +++ b/docs/bidders/avocet.md @@ -0,0 +1,5 @@ +# Avocet Bidder + +Please contact Avocet at info@avocet.io if you would like to get started selling inventory via the Avocet platform. + +**Note:** Avocet is disabled by default. Please enable it in the app config if you wish to use it. This can be done by setting `adapters.avocet.disabled` to `false` and by setting `adapters.avocet.endpoint` to a valid Avocet endpoint url. \ No newline at end of file From 587d3a23b928505cb5c438e9b25081438a0fa612 Mon Sep 17 00:00:00 2001 From: tadam75 Date: Sat, 20 Jun 2020 19:22:25 +0200 Subject: [PATCH 489/603] Adding Smartadserver adapter (#1346) Co-authored-by: tadam --- docs/bidders/smartAdserver.md | 59 +++++++++++++++++++++++++++++ usersync/usersyncers/syncer_test.go | 1 + 2 files changed, 60 insertions(+) create mode 100644 docs/bidders/smartAdserver.md diff --git a/docs/bidders/smartAdserver.md b/docs/bidders/smartAdserver.md new file mode 100644 index 00000000000..4d2663f8a3b --- /dev/null +++ b/docs/bidders/smartAdserver.md @@ -0,0 +1,59 @@ +# Smart Adserver Bidder + +## Parameters +The `ext.smartadserver` object of impression bid requests supports the following parameters : +- "networkId" - Required. The network identifier you have been provided with. +- "siteId" - Optional. The site identifier from your campaign configuration. +- "pageId" - Optional. The page identifier from your campaign configuration. +- "formatId" - Optional. The format identifier from your campaign configuration. + +The network identifier is provided by your Account Manager. +**Note:** The site, page and format identifiers have to all be provided or all empty. + +## Examples + +Without site/page/format : +``` + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "smartadserver": { + "networkId": 73 + } + } + }] +``` + +With site/page/format : + +``` + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "smartadserver": { + "networkId": 73 + "siteId": 1, + "pageId": 2, + "formatId": 3 + } + } + }] +``` \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 1b62cd73f1c..b290425bdb8 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -83,6 +83,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, + string(openrtb_ext.BidderSmartadserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTappx): syncConfig, From f342d5071a0dbaff5e8057074dd9577f5ab80e72 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:08:51 -0400 Subject: [PATCH 490/603] Metrics for TCF 2 adoption (#1360) --- metrics/prometheus/type_conversion.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 0e5c80636db..7d11604104c 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -112,3 +112,12 @@ func tcfVersionsAsString() []string { } return valuesAsString } + +func tcfVersionsAsString() []string { + values := pbsmetrics.TCFVersions() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} From 4ce313b1af2d8d253028b04ea705a28102e2718e Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 15 Jul 2020 10:07:53 -0400 Subject: [PATCH 491/603] Add support for multiple root schain nodes (#1374) --- endpoints/openrtb2/auction_test.go | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 75d0610cb34..60e38520594 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1706,6 +1706,53 @@ func TestGetAccountID(t *testing.T) { } } +func TestSChainInvalid(t *testing.T) { + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{}, + pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + ui := uint64(1) + req := openrtb.BidRequest{ + ID: "someID", + Imp: []openrtb.Imp{ + { + ID: "imp-ID", + Banner: &openrtb.Banner{ + W: &ui, + H: &ui, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), + }, + }, + Site: &openrtb.Site{ + ID: "myID", + }, + Regs: &openrtb.Regs{ + Ext: json.RawMessage(`{"us_privacy":"abcd"}`), + }, + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + } + + errL := deps.validateRequest(&req) + + expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") + assert.ElementsMatch(t, errL, []error{expectedError}) +} + func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string From 7573c7c53fbeb0891ec418566b557531f91d0d14 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 17 Jul 2020 13:05:19 -0400 Subject: [PATCH 492/603] Facebook Only Supports App Impressions (#1396) --- adapters/audienceNetwork/facebook.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 9919c50bd7c..7049fc66d96 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -472,15 +472,11 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (* return &adapters.RequestData{}, []error{err} } - // The publisher ID is either in the app object or the site object, depending on the supply of the request so we need - // to check both + // The publisher ID is expected in the app object pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id") if err != nil { - pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id") - if err != nil { - return &adapters.RequestData{}, []error{ - errors.New("path [app|site].publisher.id not found in the request"), - } + return &adapters.RequestData{}, []error{ + errors.New("path app.publisher.id not found in the request"), } } From ef12e63129272957ab1b33d259ca1ec6b14798a1 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 22 Jul 2020 13:11:25 -0400 Subject: [PATCH 493/603] Add Outgoing Connection Metrics (#1343) --- go.mod | 1 + go.sum | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/go.mod b/go.mod index 6d604adf78a..c81996f373e 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.0.0 github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect diff --git a/go.sum b/go.sum index b40d6326e33..888fe6a3f0e 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,11 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= From 7024d1d4394158c676280282f1a8b9feedbfd08b Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Thu, 30 Jul 2020 09:23:27 -0700 Subject: [PATCH 494/603] OpenX adapter: pass optional platform (PBID-598) (#1421) --- docs/bidders/openx.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md index c366db3ab61..54a0a5b1e72 100644 --- a/docs/bidders/openx.md +++ b/docs/bidders/openx.md @@ -5,10 +5,13 @@ OpenX supports the following parameters: | property | type | required? | description | example | |----------|------|-----------|-------------|---------| | unit | string | required | The ad unit id | "10092842" | -| delDomain | string | required | The delivery domain for the customer | "sademo-d.openx.net" | +| delDomain | string | required\* | The delivery domain for the customer | "sademo-d.openx.net" | +| platform | uuid | required\* | The platform id for the customer | "a3aece0c-9e80-4316-8deb-faf804779bd1" | | customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | | customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | +\* At least one of `delDomain` or `platform` parameters is required. + If you have any questions regarding setting up, please reach out to your account manager or @@ -59,4 +62,4 @@ If you have any questions regarding setting up, please reach out to your account }, } } -``` \ No newline at end of file +``` From 4823839b52df915917584f9df876693425f49c79 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 30 Jul 2020 12:41:26 -0400 Subject: [PATCH 495/603] Adds keyvalue hb_format support (#1414) --- exchange/targeting_test.go | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index aa07ed0c77b..a355d10e2c3 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -459,3 +459,212 @@ func TestSetTargeting(t *testing.T) { } } + +type TargetingTestData struct { + Description string + TargetData targetData + Auction auction + IsApp bool + CategoryMapping map[string]string + ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string +} + +var bid123 *openrtb.Bid = &openrtb.Bid{ + Price: 1.23, +} + +var bid111 *openrtb.Bid = &openrtb.Bid{ + Price: 1.11, + DealID: "mydeal", +} +var bid084 *openrtb.Bid = &openrtb.Bid{ + Price: 0.84, +} + +var TargetingTests []TargetingTestData = []TargetingTestData{ + { + Description: "Targeting winners only (most basic targeting example)", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeWinners: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder": "appnexus", + "hb_pb": "1.20", + }, + openrtb_ext.BidderRubicon: {}, + }, + }, + }, + { + Description: "Targeting on bidders only", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeBidderKeys: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder_appnexus": "appnexus", + "hb_pb_appnexus": "1.20", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "0.80", + }, + }, + }, + }, + { + Description: "Full basic targeting with hd_format", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeWinners: true, + includeBidderKeys: true, + includeFormat: true, + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid084, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_pb": "1.20", + "hb_pb_appnexus": "1.20", + "hb_format": "banner", + "hb_format_appnexus": "banner", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "0.80", + "hb_format_rubicon": "banner", + }, + }, + }, + }, + { + Description: "Cache and deal targeting test", + TargetData: targetData{ + priceGranularity: openrtb_ext.PriceGranularityFromString("med"), + includeBidderKeys: true, + cacheHost: "cache.prebid.com", + cachePath: "cache", + }, + Auction: auction{ + winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + bid: bid123, + bidType: openrtb_ext.BidTypeBanner, + }, + openrtb_ext.BidderRubicon: { + bid: bid111, + bidType: openrtb_ext.BidTypeBanner, + }, + }, + }, + cacheIds: map[*openrtb.Bid]string{ + bid123: "55555", + bid111: "cacheme", + }, + }, + ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: { + "hb_bidder_appnexus": "appnexus", + "hb_pb_appnexus": "1.20", + "hb_cache_id_appnexus": "55555", + "hb_cache_host_appnex": "cache.prebid.com", + "hb_cache_path_appnex": "cache", + }, + openrtb_ext.BidderRubicon: { + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "1.10", + "hb_cache_id_rubicon": "cacheme", + "hb_deal_rubicon": "mydeal", + "hb_cache_host_rubico": "cache.prebid.com", + "hb_cache_path_rubico": "cache", + }, + }, + }, + }, +} + +func TestSetTargeting(t *testing.T) { + for _, test := range TargetingTests { + auc := &test.Auction + // Set rounded prices from the auction data + auc.setRoundedPrices(test.TargetData.priceGranularity) + winningBids := make(map[string]*pbsOrtbBid) + // Set winning bids from the auction data + for imp, bidsByBidder := range auc.winningBidsByBidder { + for _, bid := range bidsByBidder { + if winningBid, ok := winningBids[imp]; ok { + if winningBid.bid.Price < bid.bid.Price { + winningBids[imp] = bid + } + } else { + winningBids[imp] = bid + } + } + } + auc.winningBids = winningBids + targData := test.TargetData + targData.setTargeting(auc, test.IsApp, test.CategoryMapping) + for imp, targetsByBidder := range test.ExpectedBidTargetsByBidder { + for bidder, expected := range targetsByBidder { + assert.Equal(t, + expected, + auc.winningBidsByBidder[imp][bidder].bidTargets, + "Test: %s\nTargeting failed for bidder %s on imp %s.", + test.Description, + string(bidder), + imp) + } + } + } + +} From 55a42d1ad5a74e2432a9d21af13658c86e979520 Mon Sep 17 00:00:00 2001 From: gpolaert Date: Mon, 3 Aug 2020 19:38:59 +0200 Subject: [PATCH 496/603] feat: Add new logger module - Pubstack Analytics Module (#1331) * Pubstack Analytics V1 (#11) * V1 Pubstack (#7) * feat: Add Pubstack Logger (#6) * first version of pubstack analytics * bypass viperconfig * commit #1 * gofmt * update configuration and make the tests pass * add readme on how to configure the adapter and update the network calls * update logging and fix intake url definition * feat: Pubstack Analytics Connector * fixing go mod * fix: bad behaviour on appending path to auction url * add buffering * support bootstyrap like configuration * implement route for all the objects * supports termination signal handling for goroutines * move readme to the correct location * wording * enable configuration reload + add tests * fix logs messages * fix tests * fix log line * conclude merge * merge * update go mod Co-authored-by: Amaury Ravanel * fix duplicated channel keys Co-authored-by: Amaury Ravanel * first pass - PR reviews * rename channel* -> eventChannel * dead code * Review (#10) * use json.Decoder * update documentation * use nil instead []byte("") * clean code * do not use http.DefaultClient * fix race condition (need validation) * separate the sender and buffer logics * refactor the default configuration * remove error counter * Review GP + AR * updating default config * add more logs * remove alias fields in json * fix json serializer * close event channels Co-authored-by: Amaury Ravanel * fix race condition * first pass (pr reviews) * refactor: store enabled modules into a dedicated struct * stop goroutine * test: improve coverage * PR Review * Revert "refactor: store enabled modules into a dedicated struct" This reverts commit f57d9d61680c74244effc39a5d96d6cbb2f19f7d. # Conflicts: # analytics/config/config_test.go Co-authored-by: Amaury Ravanel --- analytics/config/config_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 310dbe1a481..6589131bd61 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -1,6 +1,7 @@ package config import ( + "github.com/stretchr/testify/assert" "net/http" "os" "testing" From 80dacf5002bbc064d43cef744b398b8621f5ac1a Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 6 Aug 2020 17:12:08 +0200 Subject: [PATCH 497/603] New bid adapter for Smaato (#1413) Co-authored-by: vikram Co-authored-by: Stephan --- docs/bidders/smaato.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/bidders/smaato.md diff --git a/docs/bidders/smaato.md b/docs/bidders/smaato.md new file mode 100644 index 00000000000..881f8f2ab54 --- /dev/null +++ b/docs/bidders/smaato.md @@ -0,0 +1,42 @@ + +# Smaato Bidder + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +### Description + +Please contact Smaato Support or prebid@smaato.com to get set up with a publisherId and adspaceId. + +### Test Parameters: + +Following example includes sample `imp` object with publisherId and adSlot which can be used to test Smaato Adapter + +``` +"imp":[ + { + "id":“1C86242D-9535-47D6-9576-7B1FE87F282C, + "banner":{ + "format":[ + { + "w":300, + "h":50 + }, + { + "w":300, + "h":250 + } + ] + }, + "ext":{ + "smaato":{ + "publisherId":"100042525", + "adspaceId":"130563103" + } + } + } + ] +``` From 7ab23a01e8c0eadf10923339135d49871e054898 Mon Sep 17 00:00:00 2001 From: Adprime <64427228+Adprime@users.noreply.github.com> Date: Thu, 6 Aug 2020 19:43:30 +0300 Subject: [PATCH 498/603] New Adprime adapter (#1418) Co-authored-by: Aiholkin --- usersync/usersyncers/syncer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index b290425bdb8..4cba70af320 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -130,6 +130,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderVASTBidder: true, openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, + openrtb_ext.BidderAdprime: true, } for bidder, config := range cfg.Adapters { From 6e82fe87e5a41af42f0e312064a1df5f4a6e8465 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 20 Aug 2020 12:59:27 -0400 Subject: [PATCH 499/603] Enable geo activation of GDPR flag (#1427) --- gdpr/impl.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gdpr/impl.go b/gdpr/impl.go index 728ed06c0d1..e2cf20e82d8 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -291,3 +291,22 @@ func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext. func (a AlwaysAllow) AMPException() bool { return false } + +// Exporting to allow for easy test setups +type AlwaysFail struct{} + +func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { + return false, nil +} + +func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { + return false, nil +} + +func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { + return false, false, false, nil +} + +func (a AlwaysFail) AMPException() bool { + return false +} From 6154213076183d1c46d21204e7066c6e3e108da7 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 25 Aug 2020 12:43:06 -0400 Subject: [PATCH 500/603] moving docs to website repo (#1443) --- docs/bidders/adtarget.md | 5 --- docs/bidders/avocet.md | 5 --- docs/bidders/kidoz.md | 9 ----- docs/bidders/openx.md | 65 ----------------------------------- docs/bidders/pubmatic.md | 33 ------------------ docs/bidders/pubnative.md | 62 --------------------------------- docs/bidders/smaato.md | 42 ---------------------- docs/bidders/smartAdserver.md | 59 ------------------------------- docs/bidders/smartrtb.md | 39 --------------------- 9 files changed, 319 deletions(-) delete mode 100644 docs/bidders/adtarget.md delete mode 100644 docs/bidders/avocet.md delete mode 100644 docs/bidders/kidoz.md delete mode 100644 docs/bidders/openx.md delete mode 100644 docs/bidders/pubmatic.md delete mode 100644 docs/bidders/pubnative.md delete mode 100644 docs/bidders/smaato.md delete mode 100644 docs/bidders/smartAdserver.md delete mode 100644 docs/bidders/smartrtb.md diff --git a/docs/bidders/adtarget.md b/docs/bidders/adtarget.md deleted file mode 100644 index b658a728a2b..00000000000 --- a/docs/bidders/adtarget.md +++ /dev/null @@ -1,5 +0,0 @@ -# Adtarget bidder - -To use the Adtarget bidder you will need an aid from an exchange account on [https://console.adtarget.com.tr](adtarget.com.tr). - -For further information, please contact kamil@adtarget.com.tr \ No newline at end of file diff --git a/docs/bidders/avocet.md b/docs/bidders/avocet.md deleted file mode 100644 index 6aa67391af4..00000000000 --- a/docs/bidders/avocet.md +++ /dev/null @@ -1,5 +0,0 @@ -# Avocet Bidder - -Please contact Avocet at info@avocet.io if you would like to get started selling inventory via the Avocet platform. - -**Note:** Avocet is disabled by default. Please enable it in the app config if you wish to use it. This can be done by setting `adapters.avocet.disabled` to `false` and by setting `adapters.avocet.endpoint` to a valid Avocet endpoint url. \ No newline at end of file diff --git a/docs/bidders/kidoz.md b/docs/bidders/kidoz.md deleted file mode 100644 index 433dd71c2ca..00000000000 --- a/docs/bidders/kidoz.md +++ /dev/null @@ -1,9 +0,0 @@ -# Kidoz Bidder - -Kidoz is exclusively for Mobile app COPPA compatible ads, 100% kid relevant and appropriate. - -In order for a company to receive bids from Kidoz, they must first open a publisher account at Kidoz.net -(https://accounts.kidoz.net/publishers/register) and accept the Kidoz Terms and Conditions and Privacy Policy. -Kidoz publishers must confirm that all of their content properties are COPPA and GDPR compliant and perform no monitoring -or tracking of U13 users in their operations. New publishers are provided a Publisher ID and AccessToken, this can also -be used to login to their dashboard at the Kidoz.net portal to monitor their account activity. diff --git a/docs/bidders/openx.md b/docs/bidders/openx.md deleted file mode 100644 index 54a0a5b1e72..00000000000 --- a/docs/bidders/openx.md +++ /dev/null @@ -1,65 +0,0 @@ -# OpenX Bidder - -OpenX supports the following parameters: - -| property | type | required? | description | example | -|----------|------|-----------|-------------|---------| -| unit | string | required | The ad unit id | "10092842" | -| delDomain | string | required\* | The delivery domain for the customer | "sademo-d.openx.net" | -| platform | uuid | required\* | The platform id for the customer | "a3aece0c-9e80-4316-8deb-faf804779bd1" | -| customFloor | number | optional | The minimum CPM price in USD | 1.50 - sets a $1.50 floor | -| customParams | object | optional | User-defined targeting key-value pairs | {key1: "v1", key2: ["v2","v3"]} | - -\* At least one of `delDomain` or `platform` parameters is required. - -If you have any questions regarding setting up, please reach out to your account manager or - - -## Test Request - -### App Impression Object -``` -{ - "id": "test-impression-id", - "banner": { - "format": [ - { - "w": 480, - "h": 300 - }, - { - "w": 480, - "h": 320 - } - ] - }, - "ext": { - "openx": { - "delDomain": "mobile-d.openx.net", - "unit": "541028953" - } - } -} -``` - - -### Web -``` -{ - "id": "div1", - "banner": { - "format": [ - { - "w": 728, - "h": 90 - } - ] - }, - "ext": { - "openx": { - "unit": "540949380", - "delDomain": "sademo-d.openx.net" - }, - } -} -``` diff --git a/docs/bidders/pubmatic.md b/docs/bidders/pubmatic.md deleted file mode 100644 index 610108b2e07..00000000000 --- a/docs/bidders/pubmatic.md +++ /dev/null @@ -1,33 +0,0 @@ -# PubMatic Bidder - -## Test Request - -The following test parameters can be used to verify that Prebid Server is working properly with the -PubMatic adapter. This example includes an `imp` object with an PubMatic test publisher ID, ad slot, -and sizes that would match with the test creative. - -``` -"imp":[ - { - "id":“"some-impression-id”, - "banner":{ - "format":[ - { - "w":300, - "h":250 - }, - { - "w":300, - "h":600 - } - ] - }, - "ext":{ - "pubmatic":{ - "publisherId":“156276”, - "adSlot":"pubmatic_test" - } - } - } - ] -``` \ No newline at end of file diff --git a/docs/bidders/pubnative.md b/docs/bidders/pubnative.md deleted file mode 100644 index a25cafe0cd5..00000000000 --- a/docs/bidders/pubnative.md +++ /dev/null @@ -1,62 +0,0 @@ -# Pubnative Bidder - -## Prerequisite -Before adding PubNative as a new bidder, there are 3 prerequisites: -- As a Publisher, you need to have Prebid Mobile SDK integrated. -- You need a configured Prebid Server (either self-hosted or hosted by 3rd party). -- You need to be integrated with Ad Server SDK (e.g. Mopub) or internal product which communicates with Prebid Mobile SDK. - -Please see [documentation](https://developers.pubnative.net/docs/prebid-adding-pubnative-as-a-bidder) for more info. - -## Configuration - -- bidder should be always set to "pubnative" (`imp.ext.pubnative`) -- zone_id (int) should be always set to 1, unless special use case agreed with our account manager. (`imp.ext.pubnative.zone_id`) -- app_auth_token (string) is unique per publisher app. Please contact our account manager to obtain yours. (`imp.ext.pubnative.app_auth_token`) - -An example is illustrated in a section below. - -## Testing - -Please consult with our Account Manager for testing. -We need to confirm that your ad request is correctly received by our system. - -The following test parameters can be used to verify that Prebid Server is working properly with the -Pubnative adapter. - -The following json can be used to do a request to prebid server for verifying its integration with Pubnative adapter. - -```json -{ - "id": "some-impression-id", - "site": { - "page": "https://good.site/url" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "pubnative": { - "zone_id": 1, - "app_auth_token": "b620e282f3c74787beedda34336a4821" - } - } - } - ], - "device": { - "os": "android", - "h": 700, - "w": 375 - }, - "tmax": 500, - "test": 1 -} -``` \ No newline at end of file diff --git a/docs/bidders/smaato.md b/docs/bidders/smaato.md deleted file mode 100644 index 881f8f2ab54..00000000000 --- a/docs/bidders/smaato.md +++ /dev/null @@ -1,42 +0,0 @@ - -# Smaato Bidder - -``` -Module Name: Smaato Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@smaato.com -``` - -### Description - -Please contact Smaato Support or prebid@smaato.com to get set up with a publisherId and adspaceId. - -### Test Parameters: - -Following example includes sample `imp` object with publisherId and adSlot which can be used to test Smaato Adapter - -``` -"imp":[ - { - "id":“1C86242D-9535-47D6-9576-7B1FE87F282C, - "banner":{ - "format":[ - { - "w":300, - "h":50 - }, - { - "w":300, - "h":250 - } - ] - }, - "ext":{ - "smaato":{ - "publisherId":"100042525", - "adspaceId":"130563103" - } - } - } - ] -``` diff --git a/docs/bidders/smartAdserver.md b/docs/bidders/smartAdserver.md deleted file mode 100644 index 4d2663f8a3b..00000000000 --- a/docs/bidders/smartAdserver.md +++ /dev/null @@ -1,59 +0,0 @@ -# Smart Adserver Bidder - -## Parameters -The `ext.smartadserver` object of impression bid requests supports the following parameters : -- "networkId" - Required. The network identifier you have been provided with. -- "siteId" - Optional. The site identifier from your campaign configuration. -- "pageId" - Optional. The page identifier from your campaign configuration. -- "formatId" - Optional. The format identifier from your campaign configuration. - -The network identifier is provided by your Account Manager. -**Note:** The site, page and format identifiers have to all be provided or all empty. - -## Examples - -Without site/page/format : -``` - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "smartadserver": { - "networkId": 73 - } - } - }] -``` - -With site/page/format : - -``` - "imp": [{ - "id": "some-impression-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "smartadserver": { - "networkId": 73 - "siteId": 1, - "pageId": 2, - "formatId": 3 - } - } - }] -``` \ No newline at end of file diff --git a/docs/bidders/smartrtb.md b/docs/bidders/smartrtb.md deleted file mode 100644 index ffa88f663e8..00000000000 --- a/docs/bidders/smartrtb.md +++ /dev/null @@ -1,39 +0,0 @@ -# SmartRTB Bidder - -[SmartRTB](https://smrtb.com/) supports the following parameters to be present in the `ext` object of impression requests: - -- "pub_id" type string - Required. Publisher ID assigned to you. -- "zone_id" type string - Optional. Enables mapping for further settings and reporting in the Marketplace UI. -- "force_bid" type bool - Optional. If zone ID is mapped, this may be set to always return fake sample bids (banner, video) - -Please contact us to create a new Smart RTB Marketplace account, and for any assistance in configuration. -You may email info@smrtb.com for inquiries. - -## Test Request - -This sample request is our global test placement and should always return a branded banner bid. - -``` - { - "id": "abc", - "site": { - "page": "prebid.org" - }, - "imp": [{ - "id": "test", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "smartrtb": { - "pub_id": "test", - "zone_id": "N4zTDq3PPEHBIODv7cXK", - "force_bid": true - } - } - }] - } -``` From f24c7b47e4d33e7b702fd5583dfc9efc59a2b35d Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Thu, 3 Sep 2020 07:50:18 -0700 Subject: [PATCH 501/603] Add support for Account configuration (PBID-727, #1395) (#1426) --- endpoints/openrtb2/auction_benchmark_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index e55ffd11093..f22880a1be5 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -87,6 +87,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { paramValidator, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, newTestMetrics(), analyticsConf.NewPBSAnalytics(&config.Analytics{}), From 42a3fd0f67d0f0b41d5d9d1e60b96e937a45673e Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 10 Sep 2020 13:26:43 -0400 Subject: [PATCH 502/603] Pass Through First Party Context Data (#1479) --- ...valid-context-allowed-with-ext-bidder.json | 32 ++++ ...id-context-allowed-with-prebid-bidder.json | 36 ++++ ...ydata-imp-ext-multiple-prebid-bidders.json | 179 ++++++++++++++++++ openrtb_ext/bidders_test.go | 5 + 4 files changed, 252 insertions(+) create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json new file mode 100644 index 00000000000..aa205fc55ce --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json @@ -0,0 +1,32 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json new file mode 100644 index 00000000000..1616e84b416 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json @@ -0,0 +1,36 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json new file mode 100644 index 00000000000..d62afccf426 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -0,0 +1,179 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }, { + "seat": "rubicon", + "bid": [{ + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 26ebf7dc74b..bae880f07bd 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -118,3 +118,8 @@ func TestIsBidderNameReserved(t *testing.T) { assert.Equal(t, test.expected, result, test.bidder) } } + +func TestBidderListDoesNotDefineContext(t *testing.T) { + bidders := BidderList() + assert.NotContains(t, bidders, BidderNameContext) +} From c928821a7167dcac02debd066f3adddbcf4fe61a Mon Sep 17 00:00:00 2001 From: Alexey Elymanov Date: Wed, 23 Sep 2020 22:39:41 +0300 Subject: [PATCH 503/603] between adapter (#1437) Co-authored-by: Alexey Elymanov --- usersync/usersyncers/syncer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 4cba70af320..afd1b6a967f 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -131,6 +131,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, openrtb_ext.BidderAdprime: true, + openrtb_ext.BidderBetween: true, } for bidder, config := range cfg.Adapters { From fcfb301bc5f47ac302ffb6f8ef170fe2371d09b2 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 24 Sep 2020 16:34:56 -0400 Subject: [PATCH 504/603] Bidder Uniqueness Gatekeeping Test (#1506) --- openrtb_ext/bidders_test.go | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index bae880f07bd..d4917bd2ce1 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -123,3 +123,59 @@ func TestBidderListDoesNotDefineContext(t *testing.T) { bidders := BidderList() assert.NotContains(t, bidders, BidderNameContext) } + +// TestBidderUniquenessGatekeeping acts as a gatekeeper of bidder name uniqueness. If this test fails +// when you're building a new adapter, please consider choosing a different bidder name to maintain the +// current uniqueness threshold, or else start a discussion in the PR. +func TestBidderUniquenessGatekeeping(t *testing.T) { + // Get List Of Bidders + // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. + var bidders []string + for _, bidder := range BidderMap { + if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderSmartadserver { + bidders = append(bidders, string(bidder)) + } + } + + currentThreshold := 6 + measuredThreshold := minUniquePrefixLength(bidders) + + assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") + assert.LessOrEqual(t, measuredThreshold, currentThreshold) +} + +// minUniquePrefixLength measures the minimun amount of characters needed to uniquely identify +// one of the strings, or returns 0 if there are duplicates. +func minUniquePrefixLength(b []string) int { + targetingKeyMaxLength := 20 + for prefixLength := 1; prefixLength <= targetingKeyMaxLength; prefixLength++ { + if uniqueForPrefixLength(b, prefixLength) { + return prefixLength + } + } + return 0 +} + +func uniqueForPrefixLength(b []string, prefixLength int) bool { + m := make(map[string]struct{}) + + if prefixLength <= 0 { + return false + } + + for i, n := range b { + ns := string(n) + + if len(ns) > prefixLength { + ns = ns[0:prefixLength] + } + + m[ns] = struct{}{} + + if len(m) != i+1 { + return false + } + } + + return true +} From f7464a3a72d7765786ab51e8ea6baf4ae9a15368 Mon Sep 17 00:00:00 2001 From: Kushneryk Pavel Date: Thu, 1 Oct 2020 20:45:31 +0300 Subject: [PATCH 505/603] Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user --- usersync/usersyncers/syncer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index afd1b6a967f..822f262ba13 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -85,6 +85,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSovrn): syncConfig, string(openrtb_ext.BidderSmartadserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, + string(openrtb_ext.BidderSmartyAds): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTappx): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, From c95f70dde1798f8391855be38bcb72c93eba7cc2 Mon Sep 17 00:00:00 2001 From: Daniel Barrigas Date: Thu, 8 Oct 2020 18:10:26 +0100 Subject: [PATCH 506/603] Vtrack and event endpoints (#1467) --- analytics/filesystem/file_module_test.go | 1 + router/router.go | 1 + 2 files changed, 2 insertions(+) diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 0c3d3c9e6ac..05741fe8642 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -1,6 +1,7 @@ package filesystem import ( + "github.com/prebid/prebid-server/config" "net/http" "os" "strings" diff --git a/router/router.go b/router/router.go index 5b8ec014a1c..e4ed7cf78c9 100644 --- a/router/router.go +++ b/router/router.go @@ -7,6 +7,7 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/prebid/prebid-server/endpoints/events" "io/ioutil" "net" "net/http" From eefac8f9730f5ae01cf24d397438d2fa24e71d01 Mon Sep 17 00:00:00 2001 From: Cameron Rice <37162584+camrice@users.noreply.github.com> Date: Thu, 15 Oct 2020 10:09:38 -0400 Subject: [PATCH 507/603] Add bidder name key support (#1496) --- endpoints/openrtb2/video_auction_test.go | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9ede7147686..5377a0c2570 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1286,6 +1286,30 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) return deps } +func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) *endpointDeps { + theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) + deps := &endpointDeps{ + ex, + newParamsValidator(t), + &mockVideoStoredReqFetcher{}, + &mockVideoStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + theMetrics, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BidderMap, + ex.cache, + regexp.MustCompile(`[<>]`), + hardcodedResponseIPValidator{response: true}, + } + + return deps +} + func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { edep := &endpointDeps{ ex, @@ -1402,6 +1426,42 @@ func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r excha }, nil } +type mockExchangeAppendBidderNames struct { + lastRequest *openrtb.BidRequest + cache *mockCacheClient +} + +func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { + m.lastRequest = bidRequest + if debugLog != nil && debugLog.Enabled { + m.cache.called = true + } + ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s_appnexus","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) + return &openrtb.BidResponse{ + SeatBid: []openrtb.SeatBid{{ + Seat: "appnexus", + Bid: []openrtb.Bid{ + {ID: "01", ImpID: "1_0", Ext: ext}, + {ID: "02", ImpID: "1_1", Ext: ext}, + {ID: "03", ImpID: "1_2", Ext: ext}, + {ID: "04", ImpID: "1_3", Ext: ext}, + {ID: "05", ImpID: "2_0", Ext: ext}, + {ID: "06", ImpID: "2_1", Ext: ext}, + {ID: "07", ImpID: "2_2", Ext: ext}, + {ID: "08", ImpID: "3_0", Ext: ext}, + {ID: "09", ImpID: "3_1", Ext: ext}, + {ID: "10", ImpID: "3_2", Ext: ext}, + {ID: "11", ImpID: "3_3", Ext: ext}, + {ID: "12", ImpID: "3_5", Ext: ext}, + {ID: "13", ImpID: "4_0", Ext: ext}, + {ID: "14", ImpID: "5_0", Ext: ext}, + {ID: "15", ImpID: "5_1", Ext: ext}, + {ID: "16", ImpID: "5_2", Ext: ext}, + }, + }}, + }, nil +} + type mockExchangeVideoNoBids struct { lastRequest *openrtb2.BidRequest cache *mockCacheClient From 3d2d8b58d1b06f1008749810799dc2982f66ca3c Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon, 19 Oct 2020 10:07:22 -0400 Subject: [PATCH 508/603] Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) --- metrics/prometheus/type_conversion.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 7d11604104c..83022d41dd7 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -113,6 +113,33 @@ func tcfVersionsAsString() []string { return valuesAsString } +func storedDataTypesAsString() []string { + values := pbsmetrics.StoredDataTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func storedDataFetchTypesAsString() []string { + values := pbsmetrics.StoredDataFetchTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func storedDataErrorsAsString() []string { + values := pbsmetrics.StoredDataErrors() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + func tcfVersionsAsString() []string { values := pbsmetrics.TCFVersions() valuesAsString := make([]string, len(values)) From 8112e24ecd9210db1eb16f7fa1025161029638f3 Mon Sep 17 00:00:00 2001 From: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Date: Thu, 22 Oct 2020 19:13:03 +0300 Subject: [PATCH 509/603] Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo --- .../invalid-smartyads-ext-object.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json diff --git a/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json b/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json new file mode 100644 index 00000000000..77752d01edf --- /dev/null +++ b/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json @@ -0,0 +1,29 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "ext.bidder not provided", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "Awesome" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} From b10c464801318a2b5c8a77631e36c040e2387774 Mon Sep 17 00:00:00 2001 From: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Date: Thu, 22 Oct 2020 12:18:03 -0400 Subject: [PATCH 510/603] Yieldmo app support in yaml file (#1542) Co-authored-by: Winston --- adapters/yieldmo/yieldmo.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 7d7a8f22b01..61e4c455502 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -149,3 +149,13 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } return openrtb_ext.BidTypeVideo } + +func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + //default to video unless banner exists in impression + for _, imp := range imps { + if imp.ID == impId && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeVideo +} From 2c1713272088fafb06801477a7b991da735378fe Mon Sep 17 00:00:00 2001 From: Viacheslav Chimishuk Date: Thu, 5 Nov 2020 19:04:08 +0200 Subject: [PATCH 511/603] Add client/AccountID support into Adoppler adapter. (#1535) --- .../adopplertest/exemplary/multibid.json | 60 ------------------- .../adopplertest/exemplary/no-bid.json | 13 ---- 2 files changed, 73 deletions(-) delete mode 100644 adapters/adoppler/adopplertest/exemplary/multibid.json delete mode 100644 adapters/adoppler/adopplertest/exemplary/no-bid.json diff --git a/adapters/adoppler/adopplertest/exemplary/multibid.json b/adapters/adoppler/adopplertest/exemplary/multibid.json deleted file mode 100644 index 851f4c5b917..00000000000 --- a/adapters/adoppler/adopplertest/exemplary/multibid.json +++ /dev/null @@ -1,60 +0,0 @@ -{"mockBidRequest": {"id": "req1", - "imp":[{"id": "imp1", - "banner": {"w": 100, - "h": 200}, - "ext": {"bidder": {"adunit": "unit1"}}}, - {"id": "imp2", - "video": {"minduration": 120, - "mimes": ["video/mp4"]}, - "ext": {"bidder": {"adunit": "unit2"}}}, - {"id": "imp3", - "native": {"request": "{}"}, - "ext": {"bidder": {"adunit": "unit3"}}}]}, - "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", - "body": {"id": "req1-unit1", - "imp": [{"id": "imp1", - "banner": {"w": 100, "h": 200}, - "ext": {"bidder": {"adunit": "unit1"}}}]}}, - "mockResponse": {"status": 200, - "body": {"id": "req1-imp1-resp1", - "seatbid": [{"bid": [{"id": "req1-imp1-bid1", - "impid": "imp1", - "price": 0.12, - "adm": "a banner"}]}], - "cur": "USD"}}}, - {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", - "body": {"id": "req1-unit2", - "imp": [{"id": "imp2", - "video": {"minduration": 120, - "mimes": ["video/mp4"]}, - "ext": {"bidder": {"adunit": "unit2"}}}]}}, - "mockResponse": {"status": 200, - "body": {"id": "req1-imp2-resp2", - "seatbid": [{"bid": [{"id": "req1-imp2-bid1", - "impid": "imp2", - "price": 0.24, - "adm": "", - "cat": ["IAB1", "IAB2"], - "ext": {"ads": {"video": {"duration": 121}}}}]}], - "cur": "USD"}}}, - {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit3", - "body": {"id": "req1-unit3", - "imp": [{"id": "imp3", - "native": {"request": "{}"}, - "ext": {"bidder": {"adunit": "unit3"}}}]}}, - "mockResponse": {"status": 204, - "body": ""}}], - "expectedBidResponses": [{"currency": "USD", - "bids": [{"bid": {"id": "req1-imp1-bid1", - "impid": "imp1", - "price": 0.12, - "adm": "a banner"}, - "type": "banner"}]}, - {"currency": "USD", - "bids": [{"bid": {"id": "req1-imp2-bid1", - "impid": "imp2", - "price": 0.24, - "adm": "", - "cat": ["IAB1", "IAB2"], - "ext": {"ads": {"video": {"duration": 121}}}}, - "type": "video"}]}]} diff --git a/adapters/adoppler/adopplertest/exemplary/no-bid.json b/adapters/adoppler/adopplertest/exemplary/no-bid.json deleted file mode 100644 index 0e0f13586a8..00000000000 --- a/adapters/adoppler/adopplertest/exemplary/no-bid.json +++ /dev/null @@ -1,13 +0,0 @@ -{"mockBidRequest": {"id": "req1", - "imp":[{"id": "imp1", - "banner": {"w": 100, - "h": 200}, - "ext": {"bidder": {"adunit": "unit1"}}}]}, - "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", - "body": {"id": "req1-unit1", - "imp": [{"id": "imp1", - "banner": {"w": 100, "h": 200}, - "ext": {"bidder": {"adunit": "unit1"}}}]}}, - "mockResponse": {"status": 204, - "body": ""}}], - "expectedBidResponses": []} From 8d1815335529ccdf34921e24cb6ff33ab16dae71 Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Wed, 11 Nov 2020 03:59:50 -0500 Subject: [PATCH 512/603] 33Across: Add video support in adapter (#1557) --- .../33acrosstest/exemplary/simple-video.json | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 adapters/33across/33acrosstest/exemplary/simple-video.json diff --git a/adapters/33across/33acrosstest/exemplary/simple-video.json b/adapters/33across/33acrosstest/exemplary/simple-video.json new file mode 100644 index 00000000000..55337b92827 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/simple-video.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "instream" + } + } + } + ], + "site": {} + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 728, + "h": 90, + "protocols": [2], + "placement": 1, + "startdelay": -2, + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "ttx": { + "prod": "instream" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-vast-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} From 614fe62780b0cd4a5d3e25232d6fc49b937bb40d Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 19 Nov 2020 13:25:48 -0500 Subject: [PATCH 513/603] Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon --- ...valid-context-allowed-with-ext-bidder.json | 76 ++++++++++------- ...id-context-allowed-with-prebid-bidder.json | 84 +++++++++++-------- 2 files changed, 98 insertions(+), 62 deletions(-) diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json index aa205fc55ce..74dede0857f 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json @@ -1,32 +1,50 @@ { - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - "requestPayload": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "context": { - "data": { - "keywords": "prebid server example" - } - } - } + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 }] - } -} \ No newline at end of file + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "expectedBidResponse": { + "id":"some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ], + "bidid":"test bid id", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json index 1616e84b416..41461813c40 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json @@ -1,36 +1,54 @@ { - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - "requestPayload": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 12883451 - } - } - }, - "context": { - "data": { - "keywords": "prebid server example" - } - } - } + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 }] - } -} \ No newline at end of file + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "expectedBidResponse": { + "id":"some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ], + "bidid":"test bid id", + "nbr":0 + }, + "expectedReturnCode": 200 +} From 973fd7df602d3c4765c223512cffe68ec91cc890 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 1 Dec 2020 11:55:13 -0500 Subject: [PATCH 514/603] New Adapter Initialization Framework (#1532) --- usersync/usersyncers/syncer_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 822f262ba13..54cd3f87d8b 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -83,9 +83,6 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, - string(openrtb_ext.BidderSmartadserver): syncConfig, - string(openrtb_ext.BidderSmartRTB): syncConfig, - string(openrtb_ext.BidderSmartyAds): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, string(openrtb_ext.BidderTappx): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, @@ -95,7 +92,6 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, - string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, From bbc85772957456f2579c17b59665dce5b80d56bb Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 2 Dec 2020 06:40:22 -0500 Subject: [PATCH 515/603] Fix 33Across App Handling (#1602) --- .../33acrosstest/exemplary/app-banner.json | 100 ++++++++++++++++++ .../{simple-video.json => app-video.json} | 8 +- 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 adapters/33across/33acrosstest/exemplary/app-banner.json rename adapters/33across/33acrosstest/exemplary/{simple-video.json => app-video.json} (96%) diff --git a/adapters/33across/33acrosstest/exemplary/app-banner.json b/adapters/33across/33acrosstest/exemplary/app-banner.json new file mode 100644 index 00000000000..cd4ab085400 --- /dev/null +++ b/adapters/33across/33acrosstest/exemplary/app-banner.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "inview" + } + } + } + ], + "app": { + "id": "some-app-id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ssc.33across.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "ttx": { + "prod": "inview" + } + } + } + ], + "app": { + "id": "some-app-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": { + "ttx": { + "mediaType": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/33across/33acrosstest/exemplary/simple-video.json b/adapters/33across/33acrosstest/exemplary/app-video.json similarity index 96% rename from adapters/33across/33acrosstest/exemplary/simple-video.json rename to adapters/33across/33acrosstest/exemplary/app-video.json index 55337b92827..c3902107acc 100644 --- a/adapters/33across/33acrosstest/exemplary/simple-video.json +++ b/adapters/33across/33acrosstest/exemplary/app-video.json @@ -21,7 +21,9 @@ } } ], - "site": {} + "app": { + "id": "some-app-id" + } }, "httpCalls": [ @@ -49,8 +51,8 @@ } } ], - "site": { - "id": "fake-site-id" + "app": { + "id": "some-app-id" } } }, From 7d8450f796a1ba7e0a0f82a29a76b0608baceafa Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Wed, 2 Dec 2020 14:02:04 -0500 Subject: [PATCH 516/603] Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup --- .../invalid-smartyads-ext-object.json | 29 ----- adapters/avocet/avocet/exemplary/banner.json | 106 ------------------ adapters/avocet/avocet/exemplary/video.json | 104 ----------------- 3 files changed, 239 deletions(-) delete mode 100644 adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json delete mode 100644 adapters/avocet/avocet/exemplary/banner.json delete mode 100644 adapters/avocet/avocet/exemplary/video.json diff --git a/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json b/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json deleted file mode 100644 index 77752d01edf..00000000000 --- a/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "ext.bidder not provided", - "comparison": "literal" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "some-impression-id", - "tagid": "my-adcode", - "video": { - "mimes": ["video/mp4"], - "w": 640, - "h": 480, - "minduration": 120, - "maxduration": 150 - }, - "ext": "Awesome" - } - ], - "site": { - "page": "test.com" - } - }, - "httpCalls": [] -} diff --git a/adapters/avocet/avocet/exemplary/banner.json b/adapters/avocet/avocet/exemplary/banner.json deleted file mode 100644 index b5e308ea725..00000000000 --- a/adapters/avocet/avocet/exemplary/banner.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "adm": "", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5b51e49634f2021f127ff7c9", - "h": 250, - "id": "bc708396-9202-437b-b726-08b9864cb8b8", - "impid": "test-imp-id", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", - "language": "en", - "price": 15.64434783, - "w": 300 - } - ], - "seat": "TEST_SEAT_ID" - } - ] - } - } - } - ], - - "expectedBids": [ - { - "bid": { - "adm": "", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5b51e49634f2021f127ff7c9", - "h": 250, - "id": "bc708396-9202-437b-b726-08b9864cb8b8", - "impid": "test-imp-id", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", - "language": "en", - "price": 15.64434783, - "w": 300 - }, - "type": "banner" - } - ] -} diff --git a/adapters/avocet/avocet/exemplary/video.json b/adapters/avocet/avocet/exemplary/video.json deleted file mode 100644 index 2398256b0dd..00000000000 --- a/adapters/avocet/avocet/exemplary/video.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "bidid": "a0eec3aa-f9f6-42fb-9aa4-f1b5656d4f42", - "id": "749d36d7-c993-455f-aefd-ffd8a7e3ccf", - "seatbid": [ - { - "bid": [ - { - "adm": "Avocet", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5ec530e32d57fe1100f17d87", - "h": 396, - "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", - "impid": "dfp-ad--top-above-nav", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", - "language": "en", - "price": 15.64434783, - "w": 600, - "ext": { - "avocet": { - "duration": 30 - } - } - } - ], - "seat": "TEST_SEAT_ID" - } - ] - } - } - } - ], - - "expectedBids": [ - { - "bid": { - "adm": "Avocet", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5ec530e32d57fe1100f17d87", - "h": 396, - "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", - "impid": "dfp-ad--top-above-nav", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", - "language": "en", - "price": 15.64434783, - "w": 600, - "ext": { - "avocet": { - "duration": 30 - } - } - }, - "type": "video" - } - ] -} From 16804ee858f1e8800acb698443e222bdeb0e66ad Mon Sep 17 00:00:00 2001 From: egsk Date: Thu, 3 Dec 2020 11:58:01 +0300 Subject: [PATCH 517/603] Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov --- usersync/usersyncers/syncer_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 54cd3f87d8b..f09f994a7a7 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -128,7 +128,6 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, openrtb_ext.BidderAdprime: true, - openrtb_ext.BidderBetween: true, } for bidder, config := range cfg.Adapters { From 96422f815dfae11464ec6847e4fd615b4906fc4a Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Thu, 17 Dec 2020 13:51:29 -0500 Subject: [PATCH 518/603] 33Across: Add support for multi-imp requests (#1609) --- .../33acrosstest/exemplary/app-banner.json | 100 ---------------- .../33acrosstest/exemplary/app-video.json | 112 ------------------ 2 files changed, 212 deletions(-) delete mode 100644 adapters/33across/33acrosstest/exemplary/app-banner.json delete mode 100644 adapters/33across/33acrosstest/exemplary/app-video.json diff --git a/adapters/33across/33acrosstest/exemplary/app-banner.json b/adapters/33across/33acrosstest/exemplary/app-banner.json deleted file mode 100644 index cd4ab085400..00000000000 --- a/adapters/33across/33acrosstest/exemplary/app-banner.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "bidder": { - "siteId": "fake-site-id", - "productId": "inview" - } - } - } - ], - "app": { - "id": "some-app-id" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://ssc.33across.com", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "ttx": { - "prod": "inview" - } - } - } - ], - "app": { - "id": "some-app-id" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "h": 90, - "w": 728, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }] - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 728, - "h": 90, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/33across/33acrosstest/exemplary/app-video.json b/adapters/33across/33acrosstest/exemplary/app-video.json deleted file mode 100644 index c3902107acc..00000000000 --- a/adapters/33across/33acrosstest/exemplary/app-video.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 728, - "h": 90, - "protocols": [2], - "placement": 1, - "startdelay": -2, - "playbackmethod": [2], - "mimes": ["foo", "bar"] - }, - "ext": { - "bidder": { - "siteId": "fake-site-id", - "productId": "instream" - } - } - } - ], - "app": { - "id": "some-app-id" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://ssc.33across.com", - "body": { - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id", - "video": { - "w": 728, - "h": 90, - "protocols": [2], - "placement": 1, - "startdelay": -2, - "playbackmethod": [2], - "mimes": ["foo", "bar"] - }, - "ext": { - "ttx": { - "prod": "instream" - } - } - } - ], - "app": { - "id": "some-app-id" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-vast-ad", - "crid": "crid_10", - "h": 90, - "w": 728, - "ext": { - "ttx": { - "mediaType": "video" - } - } - }] - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-vast-ad", - "crid": "crid_10", - "w": 728, - "h": 90, - "ext": { - "ttx": { - "mediaType": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} From e752297977b98c8b299675d92f43d66fe56d5459 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 5 Jan 2021 10:21:33 -0500 Subject: [PATCH 519/603] Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) --- endpoints/auction_test.go | 4 ---- endpoints/cookie_sync_test.go | 4 ---- endpoints/setuid_test.go | 4 ---- 3 files changed, 12 deletions(-) diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index af15e8a253a..17ed7f74f45 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -458,10 +458,6 @@ func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder return m.allowPI, m.allowGeo, m.allowID, nil } -func (m *auctionMockPermissions) AMPException() bool { - return false -} - func TestBidSizeValidate(t *testing.T) { bids := make(pbs.PBSBidSlice, 0) // bid1 will be rejected due to undefined size when adunit has multiple sizes diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index cbfefe64141..d4b89a15118 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -257,7 +257,3 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } - -func (g *gdprPerms) AMPException() bool { - return false -} diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index e937eee6b62..0d68c15bea8 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -443,10 +443,6 @@ func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrt return g.allowPI, g.allowPI, g.allowPI, nil } -func (g *mockPermsSetUID) AMPException() bool { - return false -} - func newFakeSyncer(familyName string) usersync.Usersyncer { return fakeSyncer{ familyName: familyName, From 10b42e622092942cef94529c6cb2c6cdc97dee98 Mon Sep 17 00:00:00 2001 From: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Date: Tue, 5 Jan 2021 23:28:19 +0200 Subject: [PATCH 520/603] New Adapter: Mobfox (#1585) Co-authored-by: mobfox --- .../mobfoxpbtest/exemplary/simple-banner.json | 132 ++++++++++++++++++ .../mobfoxpbtest/exemplary/simple-video.json | 119 ++++++++++++++++ .../exemplary/simple-web-banner.json | 130 +++++++++++++++++ .../mobfoxpbtest/params/race/banner.json | 3 + .../mobfoxpbtest/params/race/video.json | 3 + 5 files changed, 387 insertions(+) create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video.json diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..b1936661a71 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "6", + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } +}, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "6", + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json new file mode 100644 index 00000000000..6cdcdc5a6cc --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "tagid": "7", + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..bba728ac1e9 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "8", + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "8", + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json new file mode 100644 index 00000000000..dbdac1ad995 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "6" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json new file mode 100644 index 00000000000..6e2e0b3803b --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "7" +} \ No newline at end of file From ccf57f97ac259fa94eee9db15c6908638b505086 Mon Sep 17 00:00:00 2001 From: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Date: Tue, 12 Jan 2021 16:11:52 -0500 Subject: [PATCH 521/603] New Adapter: Revcontent (#1622) --- usersync/usersyncers/syncer_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index f09f994a7a7..10a95fb4b67 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -127,7 +127,6 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderVASTBidder: true, openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, - openrtb_ext.BidderAdprime: true, } for bidder, config := range cfg.Adapters { From 41bb4ea6b94cc930c1d36a59d9c04ea532aac0b8 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Wed, 20 Jan 2021 07:49:05 -0500 Subject: [PATCH 522/603] Audit beachfront tests and change some videoResponseType details (#1638) --- .../exemplary/adm-video-by-default.json | 123 +++++ .../exemplary/adm-video-by-explicit-type.json | 124 +++++ .../banner-and-adm-video-by-default.json | 187 +++++++ ...banner-and-adm-video-by-explicit-type.json | 188 +++++++ .../exemplary/banner-and-nurl-video.json | 189 +++++++ ...response-order--banner-adm-nurl--PASS.json | 465 ++++++++++++++++++ 6 files changed, 1276 insertions(+) create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json create mode 100644 adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json create mode 100644 adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json create mode 100644 adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/six-nine-combo--response-order--banner-adm-nurl--PASS.json diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json new file mode 100644 index 00000000000..d3fa41a23c5 --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json new file mode 100644 index 00000000000..8c05d65a3b1 --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "videoResponseType": "adm", + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json b/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json new file mode 100644 index 00000000000..bff1b76a688 --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "banner-and-video", + "imp": [ + { + "id": "mix1", + "ext": { + "bidder": { + "bidfloor": 0.41, + "appIds": { + "banner": "bannerAppId1", + "video": "videoAppId1" + } + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "mix1", + "id": "bannerAppId1", + "bidfloor": 0.41, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "referrer": "", + "search": "", + "secure": 1, + "deviceOs": "", + "deviceModel": "", + "isMobile": 0, + "ua": "", + "ip": "", + "dnt": 0, + "user": {}, + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.1", + "requestId": "banner-and-video" + } + }, + "mockResponse": { + "status": 200, + "body": [ + { + "crid": "crid_1", + "price": 9.5019655, + "w": 300, + "h": 250, + "slot": "mix1", + "adm": "
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - NURL: "http://test.com/syspixel?__ads=ip8070-3PJ4q4QyZxnHE6woGe1sQ3&__adt=4122105428549383603&__ade=2&type=tracking&rqc=0w23qR-q7O7MsGkWlR9wOBm8qL7msKBtSKRJV3Pw0a0tZ47xJTnT2JwzqvXgrzPZOLZfI__68S9kCKELawQtZcO6kMyvlPM55uCaRZWng_j5btuPaEuXyA&pab=true", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "{\"type\":\"ID\",\"bid_id\":\"8255649814109237089\",\"placement_id\":\"1995257847363113_1997038003851764\",\"resolved_placement_id\":\"1995257847363113_1997038003851764\",\"sdk_version\":\"4.25.0-appnexus.bidding\",\"device_id\":\"87ECBA49-908A-428F-9DE7-4B9CED4F486C\",\"template\":7,\"payload\":\"null\"}", @@ -103,7 +94,7 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[2] = &CacheObject{ + cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "", @@ -111,14 +102,10 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[3] = &CacheObject{ + cobj[2] = &CacheObject{ IsVideo: true, Value: "", } - cobj[4] = &CacheObject{ - IsVideo: true, - Value: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - } InitPrebidCache(server.URL) ctx := context.TODO() @@ -136,12 +123,6 @@ func TestPrebidClient(t *testing.T) { if cobj[2].UUID != "UUID-3" { t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) } - if cobj[3].UUID != "UUID-4" { - t.Errorf("Fourth object UUID was '%s', should have been 'UUID-4'", cobj[3].UUID) - } - if cobj[4].UUID != "UUID-5" { - t.Errorf("Fifth object UUID was '%s', should have been 'UUID-5'", cobj[4].UUID) - } delay = 5 * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) diff --git a/router/router.go b/router/router.go index e4ed7cf78c9..400b71a4318 100644 --- a/router/router.go +++ b/router/router.go @@ -30,7 +30,6 @@ import ( "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rubicon" @@ -182,7 +181,6 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { "pulsepoint": pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint), "rubicon": rubicon.NewRubiconLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), - "lifestreet": lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint), "conversant": conversant.NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), "adform": adform.NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), "sovrn": sovrn.NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/lifestreet.yaml deleted file mode 100644 index 34dc4eca2d9..00000000000 --- a/static/bidder-info/lifestreet.yaml +++ /dev/null @@ -1,11 +0,0 @@ -maintainer: - email: "mobile.tech@lifestreet.com" -gvlVendorID: 67 -capabilities: - app: - mediaTypes: - - banner - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-params/lifestreet.json b/static/bidder-params/lifestreet.json deleted file mode 100644 index 2190d761e69..00000000000 --- a/static/bidder-params/lifestreet.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Lifestreet Adapter Params", - "description": "A schema which validates params accepted by the Lifestreet adapter", - "type": "object", - "properties": { - "slot_tag": { - "type": "string", - "description": "A tag which identifies the ad slot" - } - }, - "required": ["slot_tag"] -} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1c4e809e72b..a3f32320796 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -51,7 +51,6 @@ import ( "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" @@ -149,7 +148,6 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index a296fca6678..38d7b89d96e 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -60,7 +60,6 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderJixie): syncConfig, string(openrtb_ext.BidderKrushmedia): syncConfig, - string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, From 4adc40bac334d5ab22de03b0ffb087bbf5ed409b Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:53:26 +0300 Subject: [PATCH 575/603] New Adapter: E-Volution (#1868) --- adapters/e_volution/evolution.go | 112 ++++++++++ adapters/e_volution/evolution_test.go | 18 ++ .../exemplary/banner-without-mediatype.json | 168 +++++++++++++++ .../evolutiontest/exemplary/banner.json | 174 +++++++++++++++ .../evolutiontest/exemplary/native.json | 181 ++++++++++++++++ .../evolutiontest/exemplary/video.json | 200 ++++++++++++++++++ .../evolutiontest/params/race/banner.json | 3 + .../evolutiontest/params/race/native.json | 3 + .../evolutiontest/params/race/video.json | 3 + .../supplemental/bad-response.json | 158 ++++++++++++++ .../supplemental/empty-seatbid.json | 149 +++++++++++++ .../supplemental/status-204.json | 126 +++++++++++ .../supplemental/status-400.json | 133 ++++++++++++ .../supplemental/status-503.json | 125 +++++++++++ .../supplemental/unexpected-status.json | 132 ++++++++++++ adapters/e_volution/params_test.go | 49 +++++ adapters/e_volution/usersync.go | 12 ++ adapters/e_volution/usersync_test.go | 33 +++ config/config.go | 2 + static/bidder-info/e_volution.yaml | 14 ++ static/bidder-params/e_volution.json | 13 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 23 files changed, 1811 insertions(+) create mode 100644 adapters/e_volution/evolution.go create mode 100644 adapters/e_volution/evolution_test.go create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/native.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/video.json create mode 100644 adapters/e_volution/evolutiontest/params/race/banner.json create mode 100644 adapters/e_volution/evolutiontest/params/race/native.json create mode 100644 adapters/e_volution/evolutiontest/params/race/video.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/bad-response.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-204.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-400.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-503.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/unexpected-status.json create mode 100644 adapters/e_volution/params_test.go create mode 100644 adapters/e_volution/usersync.go create mode 100644 adapters/e_volution/usersync_test.go create mode 100644 static/bidder-info/e_volution.yaml create mode 100644 static/bidder-params/e_volution.json diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go new file mode 100644 index 00000000000..26df301cdb7 --- /dev/null +++ b/adapters/e_volution/evolution.go @@ -0,0 +1,112 @@ +package evolution + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + URI string +} + +type bidExt struct { + MediaType openrtb_ext.BidType `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.URI, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, nil + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderRawResponse.StatusCode), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad response, %s", err), + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Empty seatbid"), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := range sb.Bid { + var bidType openrtb_ext.BidType + var bidExt bidExt + if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { + bidType = openrtb_ext.BidTypeBanner + } else { + bidType = bidExt.MediaType + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + return bidResponse, nil +} diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go new file mode 100644 index 00000000000..1d2ee7ef9a6 --- /dev/null +++ b/adapters/e_volution/evolution_test.go @@ -0,0 +1,18 @@ +package evolution + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderEVolution, config.Adapter{ + Endpoint: "http://service.e-volution.ai/pbserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "evolutiontest", bidder) +} diff --git a/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json new file mode 100644 index 00000000000..251fe8c6f87 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/banner.json b/adapters/e_volution/evolutiontest/exemplary/banner.json new file mode 100644 index 00000000000..68fda4907e2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/native.json b/adapters/e_volution/evolutiontest/exemplary/native.json new file mode 100644 index 00000000000..724f55f6b8b --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/native.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/video.json b/adapters/e_volution/evolutiontest/exemplary/video.json new file mode 100644 index 00000000000..f7a03146918 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/video.json @@ -0,0 +1,200 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "bidid": "ccbd63285c0e7b69602d90319bda6be4", + "seatbid": [{ + "bid": [{ + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/banner.json b/adapters/e_volution/evolutiontest/params/race/banner.json new file mode 100644 index 00000000000..0a04c95b072 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test_banner" +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/native.json b/adapters/e_volution/evolutiontest/params/race/native.json new file mode 100644 index 00000000000..032b9dd56d8 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test_native" +} diff --git a/adapters/e_volution/evolutiontest/params/race/video.json b/adapters/e_volution/evolutiontest/params/race/video.json new file mode 100644 index 00000000000..87071003920 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test_video" +} diff --git a/adapters/e_volution/evolutiontest/supplemental/bad-response.json b/adapters/e_volution/evolutiontest/supplemental/bad-response.json new file mode 100644 index 00000000000..75f3cb455af --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/bad-response.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..c9a103aea39 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "Empty seatbid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-204.json b/adapters/e_volution/evolutiontest/supplemental/status-204.json new file mode 100644 index 00000000000..85e89873fd2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-204.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-400.json b/adapters/e_volution/evolutiontest/supplemental/status-400.json new file mode 100644 index 00000000000..b26e827200e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-400.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-503.json b/adapters/e_volution/evolutiontest/supplemental/status-503.json new file mode 100644 index 00000000000..0f289ea8d3e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-503.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..5d0df32383e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 401 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go new file mode 100644 index 00000000000..2d3602fd72b --- /dev/null +++ b/adapters/e_volution/params_test.go @@ -0,0 +1,49 @@ +package evolution + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "24" }`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderEVolution, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected evolution params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderEVolution, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/e_volution/usersync.go b/adapters/e_volution/usersync.go new file mode 100644 index 00000000000..f22784d018b --- /dev/null +++ b/adapters/e_volution/usersync.go @@ -0,0 +1,12 @@ +package evolution + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewEvolutionSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("e_volution", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/e_volution/usersync_test.go b/adapters/e_volution/usersync_test.go new file mode 100644 index 00000000000..d7a3eba5f0a --- /dev/null +++ b/adapters/e_volution/usersync_test.go @@ -0,0 +1,33 @@ +package evolution + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewEvolutionSyncer(t *testing.T) { + syncURL := "https://sync.test.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewEvolutionSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://sync.test.com/pbserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 4a2912aaa8d..45c0e6660f3 100644 --- a/config/config.go +++ b/config/config.go @@ -609,6 +609,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEVolution, "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3De_volution%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderEpom doesn't have a good default. @@ -872,6 +873,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb") v.SetDefault("adapters.epom.disabled", true) + v.SetDefault("adapters.e_volution.endpoint", "http://service.e-volution.ai/pbserver") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml new file mode 100644 index 00000000000..6ea9dc7bac2 --- /dev/null +++ b/static/bidder-info/e_volution.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "admin@e-volution.ai" +gvlVendorID: 957 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/e_volution.json b/static/bidder-params/e_volution.json new file mode 100644 index 00000000000..18de2a6062d --- /dev/null +++ b/static/bidder-params/e_volution.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "E-volution Adapter Params", + "description": "A schema which validates params accepted by the E-volution adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index a3f32320796..88752f4d7d7 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -39,6 +39,7 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -136,6 +137,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderEVolution, evolution.NewEvolutionSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAudienceNetwork, audienceNetwork.NewFacebookSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 38d7b89d96e..b84947c53e9 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -51,6 +51,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig, string(openrtb_ext.BidderEPlanning): syncConfig, + string(openrtb_ext.BidderEVolution): syncConfig, string(openrtb_ext.BidderGamma): syncConfig, string(openrtb_ext.BidderGamoshi): syncConfig, string(openrtb_ext.BidderGrid): syncConfig, From 9af0a240b832661c959b8e042edd6400a537ce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Thu, 10 Jun 2021 18:55:20 +0200 Subject: [PATCH 576/603] [criteo] accept zoneId and networkId alternate case (#1869) --- .../supplemental/multislots-alt-case.json | 232 ++++++++++++++++++ adapters/criteo/params_test.go | 7 + static/bidder-params/criteo.json | 78 +++--- 3 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 adapters/criteo/criteotest/supplemental/multislots-alt-case.json diff --git a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json new file mode 100644 index 00000000000..beb855e3f2b --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json @@ -0,0 +1,232 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 123456, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 7891011, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 121314, + "networkId": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index 73ace617b2d..9c836769aca 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -41,9 +41,13 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"zoneid": 123456}`, + `{"zoneId": 123456}`, `{"networkid": 78910}`, + `{"networkId": 78910}`, `{"zoneid": 123456, "networkid": 78910}`, + `{"zoneId": 123456, "networkId": 78910}`, `{"zoneid": 0, "networkid": 0}`, + `{"zoneId": 0, "networkId": 0}`, } var invalidParams = []string{ @@ -55,8 +59,11 @@ var invalidParams = []string{ `[]`, `{}`, `{"zoneid": -123}`, + `{"zoneId": -123}`, `{"networkid": -321}`, + `{"networkId": -321}`, `{"zoneid": -123, "networkid": -321}`, + `{"zoneId": -123, "networkId": -321}`, `{"zoneid": -1}`, `{"networkid": -1}`, `{"zoneid": -1, "networkid": -1}`, diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json index 9d348a7eded..88c6fba5d3a 100644 --- a/static/bidder-params/criteo.json +++ b/static/bidder-params/criteo.json @@ -1,30 +1,50 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Criteo adapter params", - "description": "The schema to validate Criteo specific params accepted by Criteo adapter", - "type": "object", - "properties": { - "zoneid": { - "type": "number", - "description": "Impression's zone ID.", - "minimum": 0 - }, - "networkid": { - "type": "number", - "description": "Impression's network ID.", - "minimum": 0 - } - }, - "anyOf": [ - { - "required": [ - "zoneid" - ] - }, - { - "required": [ - "networkid" - ] - } - ] +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Criteo adapter params", + "description": "The schema to validate Criteo specific params accepted by Criteo adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "integer", + "description": "Impression's zone ID.", + "minimum": 0 + }, + "zoneId": { + "type": "integer", + "description": "Impression's zone ID, preferred.", + "minimum": 0 + }, + "networkid": { + "type": "integer", + "description": "Impression's network ID.", + "minimum": 0 + }, + "networkId": { + "type": "integer", + "description": "Impression's network ID, preferred.", + "minimum": 0 + } + }, + "anyOf": [ + { + "required": [ + "zoneid" + ] + }, + { + "required": [ + "zoneId" + ] + }, + { + "required": [ + "networkid" + ] + }, + { + "required": [ + "networkId" + ] + } + ] } \ No newline at end of file From 07e3fa47392134dd3420eb3089282fbcbfcd69c8 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 10 Jun 2021 13:57:51 -0400 Subject: [PATCH 577/603] Request Provided Currency Rates (#1753) --- currency/aggregate_conversions.go | 41 +++ currency/aggregate_conversions_test.go | 89 +++++++ currency/constant_rates.go | 4 +- currency/errors.go | 13 + currency/rates.go | 14 +- endpoints/openrtb2/auction.go | 29 +++ endpoints/openrtb2/auction_test.go | 245 +++++++++++++++++- .../no-account/not-required-no-acct.json | 1 + .../with-account/required-with-acct.json | 1 + .../aliased/multiple-alias.json | 1 + .../sample-requests/aliased/simple.json | 25 +- .../errors/conversion-disabled.json | 46 ++++ ...rates-currency-missing-usepbs-default.json | 52 ++++ ...m-rates-currency-missing-usepbs-false.json | 53 ++++ .../custom-rates-empty-usepbs-false.json | 49 ++++ .../custom-rates-invalid-usepbs-false.json | 53 ++++ .../valid/conversion-disabled.json | 62 +++++ ...om-rate-not-found-usepbsrates-default.json | 70 +++++ ...om-rates-override-usepbsrates-default.json | 69 +++++ ...stom-rates-override-usepbsrates-false.json | 70 +++++ ...ustom-rates-override-usepbsrates-true.json | 70 +++++ .../valid/reverse-currency-conversion.json | 66 +++++ .../valid/server-rates-usepbsrates-true.json | 70 +++++ .../errors/no-conversion-found.json | 38 +++ .../server-rates/valid/simple-conversion.json | 55 ++++ .../disabled/good/partial.json | 1 + .../valid-fpd-allowed-with-ext-bidder.json | 3 +- .../valid-fpd-allowed-with-prebid-bidder.json | 3 +- .../valid-native/asset-img-no-hmin.json | 25 +- .../valid-native/asset-img-no-wmin.json | 25 +- .../valid-native/asset-with-id.json | 1 + .../valid-native/asset-with-no-id.json | 1 + .../valid-native/assets-with-unique-ids.json | 1 + .../context-product-compatible-subtype.json | 25 +- .../context-social-compatible-subtype.json | 25 +- .../eventtracker-exchange-specific.json | 3 +- .../valid-native/request-no-context.json | 1 + .../valid-native/request-plcmttype-empty.json | 1 + .../video-asset-event-tracker.json | 1 + .../valid-native/with-video-asset.json | 1 + .../valid-whole/exemplary/all-ext.json | 1 + .../valid-whole/exemplary/prebid-test-ad.json | 1 + .../valid-whole/exemplary/skadn.json | 3 +- exchange/bidder_test.go | 20 +- exchange/exchange.go | 30 ++- go.mod | 2 +- go.sum | 3 +- 47 files changed, 1368 insertions(+), 95 deletions(-) create mode 100644 currency/aggregate_conversions.go create mode 100644 currency/aggregate_conversions_test.go create mode 100644 currency/errors.go create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go new file mode 100644 index 00000000000..53c5ebff4b6 --- /dev/null +++ b/currency/aggregate_conversions.go @@ -0,0 +1,41 @@ +package currency + +// AggregateConversions contains both the request-defined currency rate +// map found in request.ext.prebid.currency and the currencies conversion +// rates fetched with the RateConverter object defined in rate_converter.go +// It implements the Conversions interface. +type AggregateConversions struct { + customRates, serverRates Conversions +} + +// NewAggregateConversions expects both customRates and pbsRates to not be nil +func NewAggregateConversions(customRates, pbsRates Conversions) *AggregateConversions { + return &AggregateConversions{ + customRates: customRates, + serverRates: pbsRates, + } +} + +// GetRate returns the conversion rate between two currencies prioritizing +// the customRates currency rate over that of the PBS currency rate service +// returns an error if both Conversions objects return error. +func (re *AggregateConversions) GetRate(from string, to string) (float64, error) { + rate, err := re.customRates.GetRate(from, to) + if err == nil { + return rate, nil + } else if _, isMissingRateErr := err.(ConversionRateNotFound); !isMissingRateErr { + // other error, return the error + return 0, err + } + + // because the custom rates' GetRate() call returned an error other than "conversion + // rate not found", there's nothing wrong with the 3 letter currency code so let's + // try the PBS rates instead + return re.serverRates.GetRate(from, to) +} + +// GetRates is not implemented for AggregateConversions . There is no need to call +// this function for this scenario. +func (r *AggregateConversions) GetRates() *map[string]map[string]float64 { + return nil +} diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go new file mode 100644 index 00000000000..35ca51a1fe7 --- /dev/null +++ b/currency/aggregate_conversions_test.go @@ -0,0 +1,89 @@ +package currency + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGroupedGetRate(t *testing.T) { + + // Setup: + customRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 3.00, + "EUR": 2.00, + }, + }) + + pbsRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 4.00, + "MXN": 10.00, + }, + }) + aggregateConversions := NewAggregateConversions(customRates, pbsRates) + + // Test cases: + type aTest struct { + desc string + from string + to string + expectedRate float64 + } + + testGroups := []struct { + expectedError error + testCases []aTest + }{ + { + expectedError: nil, + testCases: []aTest{ + {"Found in both, return custom rate", "USD", "GBP", 3.00}, + {"Found in both, return inverse custom rate", "GBP", "USD", 1 / 3.00}, + {"Found in custom rates only", "USD", "EUR", 2.00}, + {"Found in PBS rates only", "USD", "MXN", 10.00}, + {"Found in PBS rates only, return inverse", "MXN", "USD", 1 / 10.00}, + {"Same currency, return unitary rate", "USD", "USD", 1}, + }, + }, + { + expectedError: errors.New("currency: tag is not well-formed"), + testCases: []aTest{ + {"From-currency three-digit code malformed", "XX", "EUR", 0}, + {"To-currency three-digit code malformed", "GBP", "", 0}, + {"Both currencies malformed", "", "", 0}, + }, + }, + { + expectedError: errors.New("currency: tag is not a recognized currency"), + testCases: []aTest{ + {"From-currency three-digit code not found", "FOO", "EUR", 0}, + {"To-currency three-digit code not found", "GBP", "BAR", 0}, + }, + }, + { + expectedError: ConversionRateNotFound{"GBP", "EUR"}, + testCases: []aTest{ + {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, + }, + }, + } + + for _, group := range testGroups { + for _, tc := range group.testCases { + // Execute: + rate, err := aggregateConversions.GetRate(tc.from, tc.to) + + // Verify: + assert.Equal(t, tc.expectedRate, rate, "conversion rate doesn't match the expected rate: %s\n", tc.desc) + if group.expectedError != nil { + assert.Error(t, err, "error doesn't match expected: %s\n", tc.desc) + } else { + assert.NoError(t, err, "err should be nil: %s\n", tc.desc) + } + } + } +} diff --git a/currency/constant_rates.go b/currency/constant_rates.go index 26471a966a5..dde317d809e 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -1,8 +1,6 @@ package currency import ( - "fmt" - "golang.org/x/text/currency" ) @@ -29,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go new file mode 100644 index 00000000000..d764c15b984 --- /dev/null +++ b/currency/errors.go @@ -0,0 +1,13 @@ +package currency + +import "fmt" + +// ConversionRateNotFound is thrown by the currency.Conversions GetRate(from string, to string) method +// when the conversion rate between the two currencies, nor its reciprocal, can be found. +type ConversionRateNotFound struct { + FromCur, ToCur string +} + +func (err ConversionRateNotFound) Error() string { + return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur) +} diff --git a/currency/rates.go b/currency/rates.go index a3ae5f30fd5..62914c4b2e2 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -3,7 +3,6 @@ package currency import ( "encoding/json" "errors" - "fmt" "time" "golang.org/x/text/currency" @@ -45,8 +44,11 @@ func (r *Rates) UnmarshalJSON(b []byte) error { return nil } -// GetRate returns the conversion rate between two currencies -// returns an error in case the conversion rate between the two given currencies is not in the currencies rates map +// GetRate returns the conversion rate between two currencies or: +// - An error if one of the currency strings is not well-formed +// - An error if any of the currency strings is not a recognized currency code. +// - A MissingConversionRate error in case the conversion rate between the two +// given currencies is not in the currencies rates map func (r *Rates) GetRate(from string, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) @@ -63,12 +65,12 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { if r.Conversions != nil { if conversion, present := r.Conversions[fromUnit.String()][toUnit.String()]; present { // In case we have an entry FROM -> TO - return conversion, err + return conversion, nil } else if conversion, present := r.Conversions[toUnit.String()][fromUnit.String()]; present { // In case we have an entry TO -> FROM - return 1 / conversion, err + return 1 / conversion, nil } - return 0, fmt.Errorf("Currency conversion rate not found: '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 8913e90791d..d8a7fa689b9 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -37,6 +37,7 @@ import ( "github.com/prebid/prebid-server/util/httputil" "github.com/prebid/prebid-server/util/iputil" "golang.org/x/net/publicsuffix" + "golang.org/x/text/currency" ) const storedRequestTimeoutMillis = 50 @@ -343,6 +344,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { if err := deps.validateEidPermissions(bidExt, aliases); err != nil { return []error{err} } + + if err := validateCustomRates(bidExt.Prebid.CurrencyConversions); err != nil { + return []error{err} + } } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { @@ -437,6 +442,30 @@ func validateSChains(req *openrtb_ext.ExtRequest) error { return err } +// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in +// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual +// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty. +func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error { + if bidReqCurrencyRates == nil { + return nil + } + + for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates { + // Check if fromCurrency is a valid 3-letter currency code + if _, err := currency.ParseISO(fromCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)} + } + + // Check if currencies mapped to fromCurrency are valid 3-letter currency codes + for toCurrency := range rates { + if _, err := currency.ParseISO(toCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)} + } + } + } + return nil +} + func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error { if req == nil || req.Prebid.Data == nil { return nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 112e786083a..5aeef4e34ef 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -24,6 +24,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/metrics" @@ -45,11 +46,13 @@ type testCase struct { } type testConfigValues struct { - AccountRequired bool `json:"accountRequired"` - AliasJSON string `json:"aliases"` - BlacklistedAccounts []string `json:"blacklistedAccts"` - BlacklistedApps []string `json:"blacklistedApps"` - DisabledAdapters []string `json:"disabledAdapters"` + AccountRequired bool `json:"accountRequired"` + AliasJSON string `json:"aliases"` + BlacklistedAccounts []string `json:"blacklistedAccts"` + BlacklistedApps []string `json:"blacklistedApps"` + DisabledAdapters []string `json:"disabledAdapters"` + CurrencyRates map[string]map[string]float64 `json:"currencyRates"` + MockBidder mockBidExchangeBidder `json:"mockBidder"` } func TestJsonSampleRequests(t *testing.T) { @@ -105,6 +108,22 @@ func TestJsonSampleRequests(t *testing.T) { "Requests with first party data context info found in imp[i].ext.prebid.bidder,context", "first-party-data", }, + { + "Assert we correctly use the server conversion rates when needed", + "currency-conversion/server-rates/valid", + }, + { + "Assert we correctly throw an error when no conversion rate was found in the server conversions map", + "currency-conversion/server-rates/errors", + }, + { + "Assert we correctly use request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/valid", + }, + { + "Assert we correctly validate request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/errors", + }, } for _, test := range testSuites { testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", test.sampleRequestsSubDir)) @@ -248,6 +267,7 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.BidID, actualBidResponse.BidID, "BidResponse.BidID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.NBR, actualBidResponse.NBR, "BidResponse.NBR doesn't match expected. Test: %s\n", testFile) + assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile) //Assert []SeatBid and their Bid elements independently of their order assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile) @@ -441,8 +461,10 @@ func doRequest(t *testing.T, test testCase) (int, string) { bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates) + endpoint, _ := NewEndpoint( - &mockBidExchange{}, + mockExchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1184,6 +1206,113 @@ func TestContentType(t *testing.T) { } } +func TestValidateCustomRates(t *testing.T) { + boolTrue := true + boolFalse := false + + testCases := []struct { + desc string + inBidReqCurrencies *openrtb_ext.ExtRequestCurrency + outCurrencyError error + }{ + { + desc: "nil input, no errors expected", + inBidReqCurrencies: nil, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolFalse, + }, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolTrue, + }, + outCurrencyError: nil, + }, + { + desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "FOO": 10.0, + "MXN": 0.05, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "MXN": 0.05, + "CAD": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "All 3-digit currency codes exist, expect no error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "MXN": 0.05, + }, + "MXN": { + "JPY": 10.0, + "EUR": 10.95, + }, + }, + UsePBSRates: &boolFalse, + }, + }, + } + + for _, tc := range testCases { + actualErr := validateCustomRates(tc.inBidReqCurrencies) + + assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc) + } +} + func TestValidateImpExt(t *testing.T) { type testCase struct { description string @@ -2637,7 +2766,49 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReque } type mockBidExchange struct { - gotRequest *openrtb2.BidRequest + mockBidder mockBidExchangeBidder + pbsRates map[string]map[string]float64 +} + +func newMockBidExchange(bidder mockBidExchangeBidder, mockCurrencyConversionRates map[string]map[string]float64) *mockBidExchange { + if bidder.BidCurrency == "" { + bidder.BidCurrency = "USD" + } + + return &mockBidExchange{ + mockBidder: bidder, + pbsRates: mockCurrencyConversionRates, + } +} + +// getAuctionCurrencyRates copies the logic of the exchange package for testing purposes +func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if customRates == nil { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), e.pbsRates) + } + + usePbsRates := true + if customRates.UsePBSRates != nil { + usePbsRates = *customRates.UsePBSRates + } + + if !usePbsRates { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), customRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(customRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return currency.NewRates(time.Now(), e.pbsRates) + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, customRates.ConversionRates), currency.NewRates(time.Now(), e.pbsRates)) } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext @@ -2648,6 +2819,36 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq BidID: "test bid id", NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), } + + // Use currencies inside r.BidRequest.Cur, if any, and convert currencies if needed + if len(r.BidRequest.Cur) == 0 { + r.BidRequest.Cur = []string{"USD"} + } + + var currencyFrom string = e.mockBidder.getBidCurrency() + var conversionRate float64 = 0.00 + var err error + + var requestExt openrtb_ext.ExtRequest + if len(r.BidRequest.Ext) > 0 { + if err := json.Unmarshal(r.BidRequest.Ext, &requestExt); err != nil { + return nil, fmt.Errorf("request.ext is invalid: %v", err) + } + } + + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) + for _, bidReqCur := range r.BidRequest.Cur { + if conversionRate, err = conversions.GetRate(currencyFrom, bidReqCur); err == nil { + bidResponse.Cur = bidReqCur + break + } + } + + if conversionRate == 0 { + // Can't have bids if there's not even a 1 USD to 1 USD conversion rate + return nil, errors.New("Can't produce bid with no valid currency to use or currency conversion to convert to.") + } + if len(r.BidRequest.Imp) > 0 { var SeatBidMap = make(map[string]openrtb2.SeatBid, 0) for _, imp := range r.BidRequest.Imp { @@ -2672,9 +2873,17 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq for bidderNameOrAlias := range bidderExts { if isBidderToValidate(bidderNameOrAlias) { if val, ok := SeatBidMap[bidderNameOrAlias]; ok { - val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) + val.Bid = append(val.Bid, openrtb2.Bid{ID: e.mockBidder.getBidId(bidderNameOrAlias)}) } else { - SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} + SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{ + Seat: e.mockBidder.getSeatName(bidderNameOrAlias), + Bid: []openrtb2.Bid{ + { + ID: e.mockBidder.getBidId(bidderNameOrAlias), + Price: e.mockBidder.getBidPrice() * conversionRate, + }, + }, + } } } } @@ -2687,6 +2896,24 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq return bidResponse, nil } +type mockBidExchangeBidder struct { + BidCurrency string `json:"currency"` + BidPrice float64 `json:"price"` +} + +func (bidder mockBidExchangeBidder) getBidCurrency() string { + return bidder.BidCurrency +} +func (bidder mockBidExchangeBidder) getBidPrice() float64 { + return bidder.BidPrice +} +func (bidder mockBidExchangeBidder) getSeatName(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bids", bidderNameOrAlias) +} +func (bidder mockBidExchangeBidder) getBidId(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bid", bidderNameOrAlias) +} + type brokenExchange struct{} func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { diff --git a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json index c3ab09d4883..75c859d212b 100644 --- a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json @@ -66,6 +66,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json index a72d184c81c..ae930384499 100644 --- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json @@ -68,6 +68,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json index 55e45041e6e..00906c89772 100644 --- a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json +++ b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json @@ -87,6 +87,7 @@ } ], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/simple.json index a99907ab370..677d3d8cf53 100644 --- a/endpoints/openrtb2/sample-requests/aliased/simple.json +++ b/endpoints/openrtb2/sample-requests/aliased/simple.json @@ -27,19 +27,20 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, - "seatbid": [ - { - "bid": [ - { - "id": "alias1-bid", - "impid": "", - "price": 0 - } - ], - "seat": "alias1-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "alias1-bid", + "impid": "", + "price": 0 + } + ], + "seat": "alias1-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json new file mode 100644 index 00000000000..03877031294 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json @@ -0,0 +1,46 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates is false, a conversion is needed but conversions are disabled", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json new file mode 100644 index 00000000000..6a727e9615c --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json @@ -0,0 +1,52 @@ +{ + "description": "currency in request.cur cannot be converted because conversion rate not found in either custom currency rates nor server rates. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["GBP"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json new file mode 100644 index 00000000000..5549fa9b688 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "currency in request.cur cannot be converted because usepbsrates set to false not allowing for PBS to use its rates. Default to price of 0", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 2.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json new file mode 100644 index 00000000000..f4e19f3a4c5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json @@ -0,0 +1,49 @@ +{ + "description": "usepbsrates set to false forces BidRequest to use custom currency rates but bidRequest.ext.prebid.currency.rates field is empty", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json new file mode 100644 index 00000000000..39857650f12 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "False usepbsrates forces BidRequest use custom currency rates but bidRequest.ext.prebid.currency.rates field comes with invalid currency codes", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "FOO": 10.0 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: currency code " +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json new file mode 100644 index 00000000000..0741ea4d315 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json @@ -0,0 +1,62 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates set to false, request succeeded because no conversion was needed", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 1.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json new file mode 100644 index 00000000000..fb65a852355 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json @@ -0,0 +1,70 @@ +{ + "description": "request comes with custom rates but request.cur currency is only found in the server rates. Error wasn't thrown because usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 15.00, + "EUR": 0.85 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json new file mode 100644 index 00000000000..80790a52543 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json @@ -0,0 +1,69 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json new file mode 100644 index 00000000000..ef372c1cf66 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json @@ -0,0 +1,70 @@ +{ + "description": "request.ext.prebid.currency substitutes those of the currency conversion server because usepbsrates is false", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json new file mode 100644 index 00000000000..276e8da43c2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json new file mode 100644 index 00000000000..624f0784dac --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json @@ -0,0 +1,66 @@ +{ + "description": "USD BidRequest gets converted because mockbidder bids in foreign currency, custom conversion rate is used", + "config": { + "currencyRates":{ + "USD": { + "MXN": 8.00 + } + }, + "mockBidder": { + "currency": "MXN", + "price": 20.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 10.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "nbr":0, + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json new file mode 100644 index 00000000000..929c2e0cbd5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "CAD": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json new file mode 100644 index 00000000000..dc0d7ce6042 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json @@ -0,0 +1,38 @@ +{ + "description": "bid request calls for a bid in foreign currency MXN but conversion rate is not found in the currency conversion service.", + "config": { + "currencyRates":{ + "USD": { + "GBP": 0.80 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json new file mode 100644 index 00000000000..84788d5ada1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json @@ -0,0 +1,55 @@ +{ + "description": "bid request calls for a bid in foreign currency but mockbidder bids in USD. Conversion rate is applied", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json index 3549abaa934..735e7c5ede1 100644 --- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json +++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json @@ -58,6 +58,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json index c36ae0cd41d..a4b716b2040 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json @@ -43,7 +43,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json index ad6298db39a..27e8c46d9d7 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json @@ -47,7 +47,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json index 15af8551da6..e556b15d4f2 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json index 5d986bcf755..06673bcdf32 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json index 1e55cdda63f..9b8763491a3 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json index 36a1745cb19..22ffc7f50d8 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json index 98cdeedadbe..e60e2028637 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json +++ b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json index dbf7b9c5e0d..a3b7101d8d5 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json index 41fb833d770..77e8ce10a41 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json index 501e7ef5016..214031177ca 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json +++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json @@ -22,6 +22,7 @@ "id": "req-id", "bidid": "test bid id", "nbr": 0, + "cur": "USD", "seatbid": [{ "bid": [{ "id": "appnexus-bid", @@ -32,4 +33,4 @@ }] }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json index 1ad97c8ff8f..5ebc4e697e4 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json index 88af803684d..5518b7a06bc 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json index ab192e14881..fcc7b72d62a 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json +++ b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json index 0ec3c993251..f920c52a591 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json +++ b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index f875fa880bc..46af51635f9 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -115,6 +115,7 @@ } ], "bidid":"test bid id", + "cur":"USD", "nbr":0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json index 2c6a34f569e..d592cb66fcb 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json @@ -44,6 +44,7 @@ } ], "bidid": "test bid id", + "cur":"USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json index e238f3c07c7..cb2cec992fe 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json @@ -42,7 +42,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur":"USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 5c85108b7f0..6c31865f8a4 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), + currency.ConversionRateNotFound{"JPY", "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -587,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), - errors.New("Currency conversion rate not found: 'BZD' => 'USD'"), - errors.New("Currency conversion rate not found: 'DKK' => 'USD'"), + currency.ConversionRateNotFound{"JPY", "USD"}, + currency.ConversionRateNotFound{"BZD", "USD"}, + currency.ConversionRateNotFound{"DKK", "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -719,9 +719,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -753,7 +753,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -761,7 +761,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -769,7 +769,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index 848340b2cea..7ace5aad814 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -195,7 +195,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * defer cancel() // Get currency rates conversions for the auction - conversions := e.currencyConverter.Rates() + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader) @@ -989,6 +989,34 @@ func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo return } +func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if requestRates == nil { + // No bidRequest.ext.currency field was found, use PBS rates as usual + return e.currencyConverter.Rates() + } + + // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false + // only if it's explicitly set to false + usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates + + if !usePbsRates { + // At this point, we can safely assume the ConversionRates map is not empty because + // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have + // thrown an error under such conditions. + return currency.NewRates(time.Time{}, requestRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(requestRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return e.currencyConverter.Rates() + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, requestRates.ConversionRates), e.currencyConverter.Rates()) +} + func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) { if bid != nil && bid.bid != nil && auction != nil { if id, found := auction.cacheIds[bid.bid]; found { diff --git a/go.mod b/go.mod index 5133d004ef3..b3002a7364e 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb - golang.org/x/text v0.3.3 + golang.org/x/text v0.3.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index b7c2b9bee13..f0098ebf43f 100644 --- a/go.sum +++ b/go.sum @@ -180,8 +180,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= From b0230ea5a3ec55cef98dbf2075e243560a5c7d75 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 10 Jun 2021 12:27:37 -0700 Subject: [PATCH 578/603] Debug override header (#1853) --- config/config.go | 2 + config/config_test.go | 1 + endpoints/openrtb2/video_auction.go | 18 ++--- endpoints/openrtb2/video_auction_test.go | 6 +- exchange/auction.go | 27 +++++--- exchange/auction_test.go | 50 ++++++++++++++ exchange/bidder.go | 28 +++++--- exchange/bidder_test.go | 18 +++-- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +-- exchange/cachetest/debuglog_enabled.json | 2 + exchange/exchange.go | 19 +++--- exchange/exchange_test.go | 68 +++++++++++++------ exchange/exchangetest/debuglog_enabled.json | 2 + .../debuglog_enabled_no_bids.json | 2 + 15 files changed, 186 insertions(+), 71 deletions(-) diff --git a/config/config.go b/config/config.go index 45c0e6660f3..0f81c1f16a2 100644 --- a/config/config.go +++ b/config/config.go @@ -454,6 +454,7 @@ type DefReqFiles struct { type Debug struct { TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"` + OverrideToken string `mapstructure:"override_token"` } func (cfg *Debug) validate(errs []error) []error { @@ -1000,6 +1001,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("debug.timeout_notification.log", false) v.SetDefault("debug.timeout_notification.sampling_rate", 0.0) v.SetDefault("debug.timeout_notification.fail_only", false) + v.SetDefault("debug.override_token", "") /* IPv4 /* Site Local: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 diff --git a/config/config_test.go b/config/config_test.go index 1d4c00a5cd1..84d3b4794a9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -421,6 +421,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") 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, "") } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 84f0699e011..0af3ba512bb 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -128,11 +128,13 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video) } debugLog := exchange.DebugLog{ - Enabled: strings.EqualFold(debugQuery, "true"), - CacheType: prebid_cache_client.TypeXML, - TTL: cacheTTL, - Regexp: deps.debugLogRegexp, + Enabled: strings.EqualFold(debugQuery, "true"), + CacheType: prebid_cache_client.TypeXML, + TTL: cacheTTL, + Regexp: deps.debugLogRegexp, + DebugOverride: exchange.IsDebugOverrideEnabled(r.Header.Get(exchange.DebugOverrideHeader), deps.cfg.Debug.OverrideToken), } + debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { @@ -157,7 +159,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } resolvedRequest := requestJson - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { debugLog.Data.Request = string(requestJson) if headerBytes, err := json.Marshal(r.Header); err == nil { debugLog.Data.Headers = string(headerBytes) @@ -209,7 +211,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re //create full open rtb req from full video request mergeData(videoBidReq, bidReq) // If debug query param is set, force the response to enable test flag - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { bidReq.Test = 1 } @@ -306,7 +308,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidResp.Ext = response.Ext } - if len(bidResp.AdPods) == 0 && debugLog.Enabled { + if len(bidResp.AdPods) == 0 && debugLog.DebugEnabledOrOverridden { err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) if err != nil { vo.Errors = append(vo.Errors, err) @@ -344,7 +346,7 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P } func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) { - if debugLog != nil && debugLog.Enabled { + if debugLog != nil && debugLog.DebugEnabledOrOverridden { if rawUUID, err := uuid.NewV4(); err == nil { debugLog.CacheKey = rawUUID.String() } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 5377a0c2570..2246311f317 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1041,8 +1041,10 @@ func TestHandleErrorDebugLog(t *testing.T) { Headers: "test headers string", Response: "test response string", }, - TTL: int64(3600), - Regexp: regexp.MustCompile(`[<>]`), + TTL: int64(3600), + Regexp: regexp.MustCompile(`[<>]`), + DebugOverride: false, + DebugEnabledOrOverridden: true, } handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog) diff --git a/exchange/auction.go b/exchange/auction.go index f2c37f7a8bd..2b5d6f75aeb 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -17,14 +17,21 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" ) +const ( + DebugOverrideHeader string = "x-pbs-debug-override" +) + type DebugLog struct { - Enabled bool - CacheType prebid_cache_client.PayloadType - Data DebugData - TTL int64 - CacheKey string - CacheString string - Regexp *regexp.Regexp + Enabled bool + CacheType prebid_cache_client.PayloadType + Data DebugData + TTL int64 + CacheKey string + CacheString string + Regexp *regexp.Regexp + DebugOverride bool + //little optimization, it stores value of debugLog.Enabled || debugLog.DebugOverride + DebugEnabledOrOverridden bool } type DebugData struct { @@ -47,6 +54,10 @@ func (d *DebugLog) BuildCacheString() { d.CacheString = fmt.Sprintf("%s%s%s%s", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response) } +func IsDebugOverrideEnabled(debugHeader, configOverrideToken string) bool { + return configOverrideToken != "" && debugHeader == configOverrideToken +} + func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout int, errors []error) error { if len(d.Data.Response) == 0 && len(errors) == 0 { d.Data.Response = "No response or errors created" @@ -238,7 +249,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } - if len(toCache) > 0 && debugLog != nil && debugLog.Enabled { + if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden { debugLog.CacheKey = hbCacheID debugLog.BuildCacheString() if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 1730309287c..04aef256a81 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -132,6 +132,56 @@ func TestCacheJSON(t *testing.T) { } } +func TestIsDebugOverrideEnabled(t *testing.T) { + type inTest struct { + debugHeader string + configToken string + } + type aTest struct { + desc string + in inTest + result bool + } + testCases := []aTest{ + { + desc: "test debug header is empty, config token is empty", + in: inTest{debugHeader: "", configToken: ""}, + result: false, + }, + { + desc: "test debug header is present, config token is empty", + in: inTest{debugHeader: "TestToken", configToken: ""}, + result: false, + }, + { + desc: "test debug header is empty, config token is present", + in: inTest{debugHeader: "", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, not equal", + in: inTest{debugHeader: "TestToken123", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, equal", + in: inTest{debugHeader: "TestToken", configToken: "TestToken"}, + result: true, + }, + { + desc: "test debug header is present, config token is present, not case equal", + in: inTest{debugHeader: "TestTokeN", configToken: "TestToken"}, + result: false, + }, + } + + for _, test := range testCases { + result := IsDebugOverrideEnabled(test.in.debugHeader, test.in.configToken) + assert.Equal(t, test.result, result, test.desc) + } + +} + // LoadCacheSpec reads and parses a file as a test case. If something goes wrong, it returns an error. func loadCacheSpec(filename string) (*cacheSpec, error) { specData, err := ioutil.ReadFile(filename) diff --git a/exchange/bidder.go b/exchange/bidder.go index 413e6bb67c5..8dcc9b5b856 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -49,7 +49,7 @@ 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, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -128,7 +128,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -178,19 +178,25 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B httpInfo := <-responseChannel // If this is a test bid, capture debugging info from the requests. // Write debug data to ext in case if: + // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - if accountDebugAllowed { - if bidder.config.DebugInfo.Allow { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) - } else { - debugDisabledWarning := errortypes.Warning{ - WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, - Message: "debug turned off for bidder", + if headerDebugAllowed { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugInfo := ctx.Value(DebugContextKey) + if debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) } - errs = append(errs, &debugDisabledWarning) } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 6c31865f8a4..39a5b992ea9 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -94,7 +94,7 @@ func TestSingleBidder(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -167,7 +167,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -208,7 +208,7 @@ func TestSetGPCHeader(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -246,7 +246,7 @@ func TestSetGPCHeaderNil(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -304,7 +304,7 @@ func TestMultiBidder(t *testing.T) { } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -681,6 +681,7 @@ func TestMultiCurrencies(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -826,6 +827,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -999,6 +1001,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + false, ) // Verify: @@ -1303,6 +1306,7 @@ func TestMobileNativeTypes(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) var actualValue string @@ -1316,7 +1320,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1537,7 +1541,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 3d2eb0b8e42..aec0948ddde 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -28,8 +28,8 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { + seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) if validationErrors := removeInvalidBids(request, 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 3bb43559856..06973b837c2 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 3) assert.Len(t, errs, 0) } @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 0) assert.Len(t, errs, 5) } @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 2) assert.Len(t, errs, 3) } @@ -246,7 +246,7 @@ func TestCurrencyBids(t *testing.T) { Cur: tc.brqCur, } - seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, expectedValidBids) assert.Len(t, errs, expectedErrs) } @@ -257,6 +257,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json index e6c85c57055..faba3ed690d 100644 --- a/exchange/cachetest/debuglog_enabled.json +++ b/exchange/cachetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchange.go b/exchange/exchange.go index 7ace5aad814..c5a747766e9 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -162,13 +162,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } if debugLog == nil { - debugLog = &DebugLog{Enabled: false} + debugLog = &DebugLog{Enabled: false, DebugEnabledOrOverridden: false} } requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo := requestDebugInfo && r.Account.DebugAllow - debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow + debugInfo := debugLog.DebugEnabledOrOverridden || (requestDebugInfo && r.Account.DebugAllow) + debugLog.Enabled = debugLog.DebugEnabledOrOverridden || r.Account.DebugAllow if debugInfo { ctx = e.makeDebugContext(ctx, debugInfo) @@ -197,7 +197,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * // Get currency rates conversions for the auction conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) var auc *auction var cacheErrs []error @@ -248,7 +248,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { @@ -269,7 +269,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else { bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) @@ -280,7 +280,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } - if !r.Account.DebugAllow && requestDebugInfo { + if !r.Account.DebugAllow && requestDebugInfo && !debugLog.DebugOverride { accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ Code: errortypes.AccountLevelDebugDisabledWarningCode, Message: "debug turned off for account", @@ -413,7 +413,8 @@ func (e *exchange) getAllBids( bidAdjustments map[string]float64, conversions currency.Conversions, accountDebugAllowed bool, - globalPrivacyControlHeader string) ( + globalPrivacyControlHeader string, + headerDebugAllowed bool) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -446,7 +447,7 @@ func (e *exchange) getAllBids( var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) + bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) // Add in time reporting elapsed := time.Since(start) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7a2020c1819..04072dd1a6e 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -177,8 +177,9 @@ func TestDebugBehaviour(t *testing.T) { } type debugData struct { - bidderLevelDebugAllowed bool - accountLevelDebugAllowed bool + bidderLevelDebugAllowed bool + accountLevelDebugAllowed bool + headerOverrideDebugAllowed bool } type aTest struct { @@ -193,57 +194,78 @@ func TestDebugBehaviour(t *testing.T) { desc: "test flag equals zero, ext debug flag false, no debug info expected", in: inTest{test: 0, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals zero, ext debug flag true, debug info expected", in: inTest{test: 0, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag false, debug info expected", in: inTest{test: 1, debug: false}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag true, debug info expected", in: inTest{test: 1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", in: inTest{test: 2, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: true, }, { desc: "test account level debug disabled", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + debugData: debugData{true, false, false}, generateWarnings: true, }, { - desc: "test bidder level debug disabled", + desc: "test header override enabled when all other debug options are disabled", + in: inTest{test: -1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url debug options are enabled when all other debug options are disabled", in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, - generateWarnings: true, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url and bidder debug options are enabled when account debug option is disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, false, true}, + generateWarnings: false, + }, + { + desc: "test all debug options are enabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true, true}, + generateWarnings: false, }, } @@ -323,9 +345,12 @@ func TestDebugBehaviour(t *testing.T) { WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) auctionRequest.Warnings = errL } - + debugLog := &DebugLog{} + if test.debugData.headerOverrideDebugAllowed { + debugLog = &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + } // Run test - outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog) // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) @@ -339,6 +364,11 @@ func TestDebugBehaviour(t *testing.T) { assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set") assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value") + if test.debugData.headerOverrideDebugAllowed { + assert.Empty(t, actualExt.Warnings, "warnings should be empty") + assert.Empty(t, actualExt.Errors, "errors should be empty") + } + if test.out.debugInfoIncluded { assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug) @@ -358,13 +388,13 @@ func TestDebugBehaviour(t *testing.T) { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } - if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") } - if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { if test.generateWarnings { assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") } else { @@ -2893,7 +2923,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -3072,7 +3102,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 851bda69097..8475482f35b 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json index 4823acf8f16..b9bb15df7fb 100644 --- a/exchange/exchangetest/debuglog_enabled_no_bids.json +++ b/exchange/exchangetest/debuglog_enabled_no_bids.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { From 73482d647033bf6a3213a56370b7c7c5ea05d76f Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:58:47 -0400 Subject: [PATCH 579/603] Remove GDPR TCF1 (#1854) --- config/config_test.go | 81 +++++++++- exchange/utils_test.go | 57 +++---- gdpr/gdpr.go | 4 +- gdpr/impl.go | 57 +++---- gdpr/impl_test.go | 215 +++++++++++++++----------- gdpr/vendorlist-fetching.go | 28 +--- gdpr/vendorlist-fetching_test.go | 173 ++++----------------- metrics/go_metrics_test.go | 10 -- metrics/metrics.go | 4 - metrics/prometheus/prometheus_test.go | 15 -- privacy/gdpr/policy_test.go | 5 - static/tcf1/fallback_gvl.json | 1 - 12 files changed, 273 insertions(+), 377 deletions(-) delete mode 100644 static/tcf1/fallback_gvl.json diff --git a/config/config_test.go b/config/config_test.go index 84d3b4794a9..f34bd5fa189 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -141,6 +141,8 @@ 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) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.enabled", true, cfg.GDPR.TCF2.PurposeOneTreatment.Enabled) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.access_allowed", true, cfg.GDPR.TCF2.PurposeOneTreatment.AccessAllowed) } var fullConfig = []byte(` @@ -510,6 +512,79 @@ func TestMigrateConfigFromEnv(t *testing.T) { cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) } +func TestMigrateConfigPurposeOneTreatment(t *testing.T) { + oldPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatement: + enabled: true + access_allowed: true + `) + newPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatment: + enabled: true + access_allowed: true + `) + oldAndNewPurposeOneTreatmentConfig := []byte(` + gdpr: + tcf2: + purpose_one_treatement: + enabled: false + access_allowed: true + purpose_one_treatment: + enabled: true + access_allowed: false + `) + + tests := []struct { + description string + config []byte + wantPurpose1TreatmentEnabled bool + wantPurpose1TreatmentAccessAllowed bool + }{ + { + description: "New config and old config not set", + config: []byte{}, + }, + { + description: "New config not set, old config set", + config: oldPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: true, + }, + { + description: "New config set, old config not set", + config: newPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: true, + }, + { + description: "New config and old config set", + config: oldAndNewPurposeOneTreatmentConfig, + wantPurpose1TreatmentEnabled: true, + wantPurpose1TreatmentAccessAllowed: false, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigPurposeOneTreatment(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.wantPurpose1TreatmentEnabled, v.Get("gdpr.tcf2.purpose_one_treatment.enabled").(bool), tt.description) + assert.Equal(t, tt.wantPurpose1TreatmentAccessAllowed, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed").(bool), tt.description) + } else { + assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed"), tt.description) + } + } +} + func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -569,12 +644,6 @@ func TestInvalidHostVendorID(t *testing.T) { } } -func TestInvalidFetchGVL(t *testing.T) { - cfg := newDefaultConfig(t) - cfg.GDPR.TCF1.FetchGVL = true - assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward") -} - func TestInvalidAMPException(t *testing.T) { cfg := newDefaultConfig(t) cfg.GDPR.AMPException = true diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 50636d35ccd..1788d508c31 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1447,8 +1447,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } } -func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { - tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" +func TestCleanOpenRTBRequestsGDPR(t *testing.T) { tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" trueValue, falseValue := true, false @@ -1477,19 +1476,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf1Consent, - gdprScrub: true, - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }, - }, - { - description: "Enforce - TCF 2", + description: "Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "1", @@ -1501,11 +1488,11 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Not Enforce - TCF 1", + description: "Not Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1513,36 +1500,36 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1; GDPR signal extraction error", + description: "Enforce; GDPR signal extraction error", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0{", - gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, expectError: true, }, { - description: "Enforce - TCF 1; account GDPR enabled, host GDPR setting disregarded", + description: "Enforce; account GDPR enabled, host GDPR setting disregarded", gdprAccountEnabled: &trueValue, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR disabled, host GDPR setting disregarded", + description: "Not Enforce; account GDPR disabled, host GDPR setting disregarded", gdprAccountEnabled: &falseValue, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1550,23 +1537,23 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Enforce - TCF 1; account GDPR not specified, host GDPR enabled", + description: "Enforce; account GDPR not specified, host GDPR enabled", gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR not specified, host GDPR disabled", + description: "Not Enforce; account GDPR not specified, host GDPR disabled", gdprAccountEnabled: nil, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1578,12 +1565,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, userSyncIfAmbiguous: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { @@ -1591,7 +1578,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, userSyncIfAmbiguous: true, expectPrivacyLabels: metrics.PrivacyLabels{ @@ -1604,12 +1591,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index ffd5ced462a..4179d8122ac 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -30,7 +30,6 @@ type Permissions interface { // Versions of the GDPR TCF technical specification. const ( - tcf1SpecVersion uint8 = 1 tcf2SpecVersion uint8 = 2 ) @@ -44,8 +43,7 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ cfg: cfg, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: newVendorListFetcherTCF1(cfg), - tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)}, + tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } if cfg.HostVendorID == 0 { diff --git a/gdpr/impl.go b/gdpr/impl.go index 312d60e14c5..4b39f2232f3 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/prebid/go-gdpr/api" - tcf1constants "github.com/prebid/go-gdpr/consentconstants" - consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" + "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" @@ -120,23 +120,15 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } - // InfoStorageAccess is the same across TCF 1 and TCF 2 - if parsedConsent.Version() == 2 { - if !p.cfg.TCF2.Purpose1.Enabled { - // We are not enforcing purpose 1 - return true, nil - } - consent, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err := fmt.Errorf("Unable to access TCF2 parsed consent") - return false, err - } - return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil - } - if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { + if !p.cfg.TCF2.Purpose1.Enabled { return true, nil } - return false, nil + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + err := fmt.Errorf("Unable to access TCF2 parsed consent") + return false, err + } + return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, false), nil } func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { @@ -145,44 +137,33 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, return false, false, false, err } + // vendor will be nil if not a valid TCF2 consent string if vendor == nil { return false, false, false, nil } - if parsedConsent.Version() == 2 { - if p.cfg.TCF2.Enabled { - return p.allowActivitiesTCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) - } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { - return true, true, true, nil - } - } else { - if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, true, true, nil - } + if !p.cfg.TCF2.Enabled { + return true, false, false, nil } - return true, false, false, nil -} -func (p *permissionsImpl) allowActivitiesTCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { - consent, ok := parsedConsent.(tcf2.ConsentMetadata) + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return } if p.cfg.TCF2.SpecialPurpose1.Enabled { - passGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + passGeo = consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) } else { passGeo = true } if p.cfg.TCF2.Purpose2.Enabled { - allowBidRequest = p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(2), weakVendorEnforcement) + allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), weakVendorEnforcement) } else { allowBidRequest = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), weakVendorEnforcement) { passID = true break } @@ -195,8 +176,8 @@ const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool { - if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { @@ -228,7 +209,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons } version := parsedConsent.Version() - if version < 1 || version > 2 { + if version != 2 { return } vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 3b974ffa3bb..d26c0c57231 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -23,7 +23,6 @@ func TestDisallowOnEmptyConsent(t *testing.T) { }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: failedListFetcher, tcf2SpecVersion: failedListFetcher, }, } @@ -49,106 +48,120 @@ func TestAllowOnSignalNo(t *testing.T) { } func TestAllowedSyncs(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, - {ID: 3, Purposes: []int{1}}, + vendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABCAAIAAAAAAAAAAACEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) } func TestProhibitedPurposes(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + vendor2NoPurpose1Consent := "CPGWkCaPGWkCaApAAAENABCAAAAAAAAAAAAAABEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } func TestProhibitedVendors(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + purpose1NoVendorConsent := "CPGWkCaPGWkCaApAAAENABCAAIAAAAAAAAAAABAAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -159,7 +172,6 @@ func TestMalformedConsent(t *testing.T) { HostVendorID: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(nil), tcf2SpecVersion: listFetcher(nil), }, } @@ -172,7 +184,7 @@ func TestMalformedConsent(t *testing.T) { func TestAllowActivities(t *testing.T) { bidderAllowedByConsent := openrtb_ext.BidderAppnexus bidderBlockedByConsent := openrtb_ext.BidderRubicon - consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + vendor2AndPurpose2Consent := "CPGWbY_PGWbY_GYAAAENABCAAEAAAAAAAAAAACEAAAAA" tests := []struct { description string @@ -190,7 +202,7 @@ func TestAllowActivities(t *testing.T) { publisherID: "appNexusAppID", userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -198,7 +210,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalNo, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -206,7 +218,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -222,7 +234,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: true, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -238,7 +250,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -254,31 +266,37 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: false, }, } - - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + vendorListData := MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1, 3}}, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{2}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}}, + TCF2: config.TCF2{ + Enabled: true, + Purpose2: config.PurposeDetail{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } @@ -293,10 +311,10 @@ func TestAllowActivities(t *testing.T) { } } -func buildTCF2VendorList34() tcf2VendorList { - return tcf2VendorList{ +func buildVendorList34() vendorList { + return vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{ + Vendors: map[string]*vendor{ "2": { ID: 2, Purposes: []int{1}, @@ -332,7 +350,7 @@ func buildTCF2VendorList34() tcf2VendorList { } } -var tcf2Config = config.GDPR{ +var gdprConfig = config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, @@ -343,7 +361,7 @@ var tcf2Config = config.GDPR{ }, } -type tcf2TestDef struct { +type testDef struct { description string bidder openrtb_ext.BidderName consent string @@ -353,10 +371,10 @@ type tcf2TestDef struct { weakVendorEnforcement bool } -func TestAllowActivitiesTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesGeoAndID(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -364,7 +382,6 @@ func TestAllowActivitiesTCF2(t *testing.T) { openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), 74: parseVendorListDataV2(t, vendorListData), @@ -372,8 +389,8 @@ func TestAllowActivitiesTCF2(t *testing.T) { }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 - testDefs := []tcf2TestDef{ + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, @@ -429,17 +446,16 @@ func TestAllowActivitiesTCF2(t *testing.T) { } } -func TestAllowActivitiesWhitelistTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesWhitelist(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -453,17 +469,16 @@ func TestAllowActivitiesWhitelistTCF2(t *testing.T) { assert.EqualValuesf(t, true, passID, "PassID failure") } -func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesPubRestrict(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 32, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 15: parseVendorListDataV2(t, vendorListData), }), @@ -472,7 +487,7 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, // Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent - testDefs := []tcf2TestDef{ + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, @@ -504,24 +519,23 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { } } -func TestAllowSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") @@ -531,19 +545,18 @@ func TestAllowSyncTCF2(t *testing.T) { assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedPurposeSyncTCF2(t *testing.T) { - tcf2VendorList34 := buildTCF2VendorList34() - tcf2VendorList34.Vendors["8"].Purposes = []int{7} - vendorListData := tcf2MarshalVendorList(tcf2VendorList34) +func TestProhibitedPurposeSync(t *testing.T) { + vendorList34 := buildVendorList34() + vendorList34.Vendors["8"].Purposes = []int{7} + vendorListData := MarshalVendorList(vendorList34) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -551,7 +564,7 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 8 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -561,10 +574,10 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedVendorSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestProhibitedVendorSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -572,7 +585,6 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { openrtb_ext.BidderOpenx: 10, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -580,7 +592,7 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 10 - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -713,7 +725,7 @@ func TestNormalizeGDPR(t *testing.T) { } } -func TestAllowActivitiesTCF2BidRequests(t *testing.T) { +func TestAllowActivitiesBidRequests(t *testing.T) { purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" @@ -757,7 +769,7 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { } for _, td := range testDefs { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, @@ -773,7 +785,6 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { openrtb_ext.BidderPubmatic: 6, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -787,3 +798,21 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } + +func TestTCF1Consent(t *testing.T) { + bidderAllowedByConsent := openrtb_ext.BidderAppnexus + tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + + perms := permissionsImpl{ + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + }, + } + + bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, "", SignalYes, tcf1Consent, false) + + assert.Nil(t, err, "TCF1 consent - no error returned") + assert.Equal(t, false, bidReq, "TCF1 consent - bid request not allowed") + assert.Equal(t, false, passGeo, "TCF1 consent - passing geo not allowed") + assert.Equal(t, false, passID, "TCF1 consent - passing id not allowed") +} diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index bc7eab40647..24489e73265 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -26,33 +26,7 @@ type saveVendors func(uint16, api.VendorList) // // Nothing in this file is exported. Public APIs can be found in gdpr.go -func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - if len(cfg.TCF1.FallbackGVLPath) == 0 { - return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { - return nil, makeVendorListNotFoundError(vendorListVersion) - } - } - - fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath) - return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) { - return fallback, nil - } -} - -func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList { - fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) - if err != nil { - glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) - } - - fallback, err := vendorlist.ParseEagerly(fallbackContents) - if err != nil { - glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) - } - return fallback -} - -func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { +func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { cacheSave, cacheLoad := newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 27f1bc3b996..95529e4e334 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -14,71 +14,15 @@ import ( "github.com/prebid/prebid-server/config" ) -func TestTCF1FetcherInitialLoad(t *testing.T) { - // Loads two vendor lists during initialization by setting the latest vendor list version to 2. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 2, - vendorLists: map[int]string{ - 1: tcf1VendorList1, - 2: tcf1VendorList2, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorListFallbackExpected, - }, - { - description: "Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - } - - for _, test := range testCases { - runTestTCF1(t, test, server) - } -} - -func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { +func TestFetcherDynamicLoadListExists(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor lists will be dynamically loaded. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, - 2: tcf2VendorList2, + 1: vendorList1, + 2: vendorList2, }, }))) defer server.Close() @@ -91,17 +35,17 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { expected: vendorList2Expected, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { +func TestFetcherDynamicLoadListDoesntExist(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor list load attempts will be done dynamically. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, + 1: vendorList1, }, }))) defer server.Close() @@ -116,30 +60,30 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { }, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherThrottling(t *testing.T) { +func TestFetcherThrottling(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2MarshalVendorList(tcf2VendorList{ + 1: MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1}}}, }), - 2: tcf2MarshalVendorList(tcf2VendorList{ + 2: MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, }), - 3: tcf2MarshalVendorList(tcf2VendorList{ + 3: MarshalVendorList(vendorList{ VendorListVersion: 3, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, }), }, }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) // Dynamically Load List 2 Successfully _, errList1 := fetcher(context.Background(), 2) @@ -151,7 +95,7 @@ func TestTCF2FetcherThrottling(t *testing.T) { assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2MalformedVendorlist(t *testing.T) { +func TestMalformedVendorlist(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ @@ -160,30 +104,30 @@ func TestTCF2MalformedVendorlist(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) // Fetching should fail since vendor list could not be unmarshalled. assert.Error(t, err) } -func TestTCF2ServerUrlInvalid(t *testing.T) { +func TestServerUrlInvalid(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" } - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2ServerUnavailable(t *testing.T) { +func TestServerUnavailable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") @@ -213,24 +157,14 @@ func TestVendorListURLMaker(t *testing.T) { } } -var tcf1VendorList1 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList1 = MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2}}}, }) -var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}}, -}) - -var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList2 = MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}}, -}) - -var tcf2VendorList2 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, }) var vendorList2Expected = testExpected{ @@ -245,27 +179,12 @@ var vendorListFallbackExpected = testExpected{ vendorPurposes: map[int]bool{1: true, 2: false, 3: true}, } -type tcf1VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors []tcf1Vendor `json:"vendors"` +type vendorList struct { + VendorListVersion uint16 `json:"vendorListVersion"` + Vendors map[string]*vendor `json:"vendors"` } -type tcf1Vendor struct { - ID uint16 `json:"id"` - Purposes []int `json:"purposeIds"` -} - -func tcf1MarshalVendorList(vendorList tcf1VendorList) string { - json, _ := json.Marshal(vendorList) - return string(json) -} - -type tcf2VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors map[string]*tcf2Vendor `json:"vendors"` -} - -type tcf2Vendor struct { +type vendor struct { ID uint16 `json:"id"` Purposes []int `json:"purposes"` LegIntPurposes []int `json:"legIntPurposes"` @@ -273,7 +192,7 @@ type tcf2Vendor struct { SpecialPurposes []int `json:"specialPurposes"` } -func tcf2MarshalVendorList(vendorList tcf2VendorList) string { +func MarshalVendorList(vendorList vendorList) string { json, _ := json.Marshal(vendorList) return string(json) } @@ -323,8 +242,7 @@ type test struct { } type testSetup struct { - enableTCF1Fallback bool - vendorListVersion uint16 + vendorListVersion uint16 } type testExpected struct { @@ -334,31 +252,9 @@ type testExpected struct { vendorPurposes map[int]bool } -func runTestTCF1(t *testing.T, test test, server *httptest.Server) { +func runTest(t *testing.T, test test, server *httptest.Server) { config := testConfig() - if test.setup.enableTCF1Fallback { - config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - } - - fetcher := newVendorListFetcherTCF1(config) - vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) - - if test.expected.errorMessage != "" { - assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") - } else { - assert.NoError(t, err, test.description+":vendorlist") - assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") - vendor := vendorList.Vendor(test.expected.vendorID) - for id, expected := range test.expected.vendorPurposes { - result := vendor.Purpose(consentconstants.Purpose(id)) - assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) - } - } -} - -func runTestTCF2(t *testing.T, test test, server *httptest.Server) { - config := testConfig() - fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server)) vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) if test.expected.errorMessage != "" { @@ -387,8 +283,5 @@ func testConfig() config.GDPR { InitVendorlistFetch: 60 * 1000, ActiveVendorlistFetch: 1000 * 5, }, - TCF1: config.TCF1{ - FetchGVL: true, - }, } } diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 7a636752747..ed9df52a51e 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -60,7 +60,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut) ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest) ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest) - ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1]) ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2]) ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr]) } @@ -570,25 +569,16 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: TCFVersionErr, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) m.RecordRequestPrivacy(PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: TCFVersionV2, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA") assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out") assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA") assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err") - assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") } diff --git a/metrics/metrics.go b/metrics/metrics.go index 5966b7716f3..cf86f1f5556 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -308,7 +308,6 @@ type TCFVersionValue string const ( TCFVersionErr TCFVersionValue = "err" - TCFVersionV1 TCFVersionValue = "v1" TCFVersionV2 TCFVersionValue = "v2" ) @@ -316,7 +315,6 @@ const ( func TCFVersions() []TCFVersionValue { return []TCFVersionValue{ TCFVersionErr, - TCFVersionV1, TCFVersionV2, } } @@ -324,8 +322,6 @@ func TCFVersions() []TCFVersionValue { // TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue func TCFVersionToValue(version int) TCFVersionValue { switch { - case version == 1: - return TCFVersionV1 case version == 2: return TCFVersionV2 } diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 2cdf8702364..79e4b260b96 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1410,18 +1410,10 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionErr, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) m.RecordRequestPrivacy(metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, float64(1), @@ -1456,13 +1448,6 @@ func TestRecordRequestPrivacy(t *testing.T) { versionLabel: "err", }) - assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF, - float64(2), - prometheus.Labels{ - sourceLabel: sourceRequest, - versionLabel: "v1", - }) - assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF, float64(1), prometheus.Labels{ diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index 40828cf17c7..d57791e0d64 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -17,11 +17,6 @@ func TestValidateConsent(t *testing.T) { consent: "", expected: false, }, - { - description: "TCF1 Valid", - consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expected: true, - }, { description: "TCF2 Valid", consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json deleted file mode 100644 index 9f1c8506b32..00000000000 --- a/static/tcf1/fallback_gvl.json +++ /dev/null @@ -1 +0,0 @@ -{"vendorListVersion":215,"lastUpdated":"2020-08-13T16:00:19Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Mediakeys Platform","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"},{"id":808,"name":"Pure Local Media GmbH","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://purelocalmedia.de/?page_id=593"}]} \ No newline at end of file From 2be3ddb61d757ef28c30984296a4b436faba206b Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:26:55 -0400 Subject: [PATCH 580/603] Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) --- config/config_test.go | 13 ++- endpoints/cookie_sync.go | 6 +- endpoints/cookie_sync_test.go | 4 +- exchange/exchange_test.go | 37 ++++--- exchange/targeting_test.go | 18 ++-- exchange/utils.go | 4 +- exchange/utils_test.go | 63 ++++++------ gdpr/impl.go | 2 +- gdpr/impl_test.go | 176 +++++++++++++++++----------------- 9 files changed, 174 insertions(+), 149 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index f34bd5fa189..a2b28d026d7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -148,7 +148,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - usersync_if_ambiguous: true + default_value: "0" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +352,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) - cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true) + cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "0") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -460,6 +460,9 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { func TestValidConfig(t *testing.T) { cfg := Configuration{ + GDPR: GDPR{ + DefaultValue: "1", + }, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ @@ -650,6 +653,12 @@ func TestInvalidAMPException(t *testing.T) { assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") } +func TestInvalidGDPRDefaultValue(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.GDPR.DefaultValue = "2" + assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") +} + func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { cfg := Configuration{ CurrencyConverter: CurrencyConverter{ diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 0396ee9f107..447ac2385a9 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -97,7 +97,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h } parsedReq := &cookieSyncRequest{} - if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.UsersyncIfAmbiguous); err != nil { + if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.DefaultValue); err != nil { co.Status = http.StatusBadRequest co.Errors = append(co.Errors, err) http.Error(w, co.Errors[len(co.Errors)-1].Error(), co.Status) @@ -188,7 +188,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h enc.Encode(csResp) } -func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbiguous bool) error { +func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, gdprDefaultValue string) error { if err := json.Unmarshal(bodyBytes, parsedReq); err != nil { return fmt.Errorf("JSON parsing failed: %s", err.Error()) } @@ -200,7 +200,7 @@ func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbi if parsedReq.GDPR == nil { var gdpr = new(int) *gdpr = 1 - if usersyncIfAmbiguous { + if gdprDefaultValue == "0" { *gdpr = 0 } parsedReq.GDPR = gdpr diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 7632894baf6..2f56c262979 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -110,7 +110,7 @@ func TestCCPA(t *testing.T) { } for _, test := range testCases { - gdpr := config.GDPR{UsersyncIfAmbiguous: true} + gdpr := config.GDPR{DefaultValue: "0"} ccpa := config.CCPA{Enforce: test.enforceCCPA} rr := doConfigurablePost(test.requestBody, nil, true, syncersForTest(), gdpr, ccpa) assert.Equal(t, http.StatusOK, rr.Code, test.description+":httpResponseCode") @@ -149,7 +149,7 @@ func TestCookieSyncNoBidders(t *testing.T) { } func TestCookieSyncNoCookiesBrokenGDPR(t *testing.T) { - rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{UsersyncIfAmbiguous: true}, config.CCPA{}) + rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{DefaultValue: "0"}, config.CCPA{}) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork"}, parseSyncs(t, rr.Body.Bytes())) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 04072dd1a6e..31988777b13 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1645,6 +1645,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { eeac[c] = s } + var gdprDefaultValue string + if spec.AssumeGDPRApplies { + gdprDefaultValue = "1" + } else { + gdprDefaultValue = "0" + } + privacyConfig := config.Privacy{ CCPA: config.CCPA{ Enforce: spec.EnforceCCPA, @@ -1653,9 +1660,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Enforce: spec.EnforceLMT, }, GDPR: config.GDPR{ - Enabled: spec.GDPREnabled, - UsersyncIfAmbiguous: !spec.AssumeGDPRApplies, - EEACountriesMap: eeac, + Enabled: spec.GDPREnabled, + DefaultValue: gdprDefaultValue, + EEACountriesMap: eeac, }, } bidIdGenerator := &mockBidIDGenerator{} @@ -1797,18 +1804,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } return &exchange{ - adapterMap: bidderAdapters, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), - cache: &wellBehavedCache{}, - cacheTime: 0, - gDPR: &permissionsMock{allowAllBidders: true}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous, - privacyConfig: privacyConfig, - categoriesFetcher: categoriesFetcher, - bidderInfo: bidderInfos, - externalURL: "http://localhost", - bidIDGenerator: bidIDGenerator, + adapterMap: bidderAdapters, + me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), + cache: &wellBehavedCache{}, + cacheTime: 0, + gDPR: &permissionsMock{allowAllBidders: true}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: privacyConfig.GDPR.DefaultValue, + privacyConfig: privacyConfig, + categoriesFetcher: categoriesFetcher, + bidderInfo: bidderInfos, + externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, } } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index a355d10e2c3..f7a1881f9f6 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -87,15 +87,15 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op } ex := &exchange{ - adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.DummyMetricsEngine{}, - cache: &wellBehavedCache{}, - cacheTime: time.Duration(0), - gDPR: gdpr.AlwaysAllow{}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: false, - categoriesFetcher: categoriesFetcher, - bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), + me: &metricsConf.DummyMetricsEngine{}, + cache: &wellBehavedCache{}, + cacheTime: time.Duration(0), + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: "1", + categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) diff --git a/exchange/utils.go b/exchange/utils.go index 0b7ee28f484..4cd083a628e 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - usersyncIfAmbiguous bool, + gdprDefaultValue string, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && !usersyncIfAmbiguous) + gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == "1") ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1788d508c31..dad0d69db15 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, true, privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - true, + "0", privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, true, privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1459,7 +1459,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent string gdprScrub bool permissionsError error - userSyncIfAmbiguous bool + gdprDefaultValue string expectPrivacyLabels metrics.PrivacyLabels expectError bool }{ @@ -1470,6 +1470,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: "malformed", gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", @@ -1482,6 +1483,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1494,6 +1496,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1506,6 +1509,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0{", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1519,6 +1523,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1531,6 +1536,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1543,6 +1549,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1555,32 +1562,33 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce - Ambiguous signal, don't sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: true, - userSyncIfAmbiguous: false, + description: "Enforce - Ambiguous signal, don't sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - Ambiguous signal, sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: false, - userSyncIfAmbiguous: true, + description: "Not Enforce - Ambiguous signal, sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprDefaultValue: "0", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1594,6 +1602,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1610,8 +1619,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprHostEnabled, - UsersyncIfAmbiguous: test.userSyncIfAmbiguous, + Enabled: test.gdprHostEnabled, + DefaultValue: test.gdprDefaultValue, TCF2: config.TCF2{ Enabled: true, }, @@ -1636,7 +1645,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.userSyncIfAmbiguous, + test.gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1698,8 +1707,8 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprEnforced, - UsersyncIfAmbiguous: true, + Enabled: test.gdprEnforced, + DefaultValue: "0", TCF2: config.TCF2{ Enabled: true, }, @@ -1727,7 +1736,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - true, + "0", privacyConfig, nil) diff --git a/gdpr/impl.go b/gdpr/impl.go index 4b39f2232f3..7fa35aa7d38 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -94,7 +94,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.UsersyncIfAmbiguous { + if p.cfg.DefaultValue == "0" { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d26c0c57231..345dd52621d 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -18,8 +18,8 @@ import ( func TestDisallowOnEmptyConsent(t *testing.T) { perms := permissionsImpl{ cfg: config.GDPR{ - HostVendorID: 3, - UsersyncIfAmbiguous: true, + HostVendorID: 3, + DefaultValue: "0", }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ @@ -190,84 +190,84 @@ func TestAllowActivities(t *testing.T) { description string bidderName openrtb_ext.BidderName publisherID string - userSyncIfAmbiguous bool + gdprDefaultValue string gdpr Signal consent string passID bool weakVendorEnforcement bool }{ { - description: "Allow PI - Non standard publisher", - bidderName: bidderBlockedByConsent, - publisherID: "appNexusAppID", - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - Non standard publisher", + bidderName: bidderBlockedByConsent, + publisherID: "appNexusAppID", + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with No GDPR", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalNo, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - known vendor with No GDPR", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalNo, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with Yes GDPR", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "Allow PI - known vendor with Yes GDPR", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: "", - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: "", + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: "", - passID: false, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: "", + passID: false, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: vendor2AndPurpose2Consent, - passID: true, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Don't allow PI - known vendor with Yes GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: "", - passID: false, + description: "Don't allow PI - known vendor with Yes GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: "", + passID: false, }, { - description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: false, + description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: false, }, } vendorListData := MarshalVendorList(vendorList{ @@ -302,7 +302,7 @@ func TestAllowActivities(t *testing.T) { } for _, tt := range tests { - perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous + perms.cfg.DefaultValue = tt.gdprDefaultValue _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -669,53 +669,53 @@ func assertStringsEqual(t *testing.T, expected string, actual string) { func TestNormalizeGDPR(t *testing.T) { tests := []struct { - description string - userSyncIfAmbiguous bool - giveSignal Signal - wantSignal Signal + description string + gdprDefaultValue string + giveSignal Signal + wantSignal Signal }{ { - description: "Don't normalize - Signal No and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal No and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalAmbiguous, - wantSignal: SignalYes, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalAmbiguous, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalAmbiguous, - wantSignal: SignalNo, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalAmbiguous, + wantSignal: SignalNo, }, } for _, tt := range tests { perms := permissionsImpl{ cfg: config.GDPR{ - UsersyncIfAmbiguous: tt.userSyncIfAmbiguous, + DefaultValue: tt.gdprDefaultValue, }, } From 81b94200554ff170b2087b448855bb8fc2f456ff Mon Sep 17 00:00:00 2001 From: Rachel Joyce Date: Tue, 15 Jun 2021 10:19:30 -0600 Subject: [PATCH 581/603] Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) --- adapters/sovrn/sovrn.go | 5 +- .../both-custom-default-bidfloor.json | 126 ++++++++++++++++++ .../sovrntest/supplemental/no-bidfloor.json | 122 +++++++++++++++++ .../supplemental/only-custom-bidfloor.json | 125 +++++++++++++++++ .../supplemental/only-default-bidfloor.json | 124 +++++++++++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/no-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index be1c2221ae5..40969d3638e 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -287,7 +287,10 @@ func preprocess(imp *openrtb2.Imp) (string, error) { } imp.TagID = getTagid(sovrnExt) - imp.BidFloor = sovrnExt.BidFloor + + if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 { + imp.BidFloor = sovrnExt.BidFloor + } return imp.TagID, nil } diff --git a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json new file mode 100644 index 00000000000..4b997b68266 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json new file mode 100644 index 00000000000..0aa3ad74e62 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json new file mode 100644 index 00000000000..3cd6539f988 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 4.20, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json new file mode 100644 index 00000000000..cb74e5643b6 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} From 1c83e92f2fdb6af5ba92e26b7390432028bb9aab Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 16 Jun 2021 15:23:16 -0400 Subject: [PATCH 582/603] GDPR: require host specify default value (#1859) --- config/config_test.go | 76 +++++++++++++++++++++++--------------- endpoints/auction_test.go | 1 + exchange/exchange_test.go | 7 +++- exchange/targeting_test.go | 2 +- exchange/utils.go | 4 +- exchange/utils_test.go | 21 +++++++---- gdpr/gdpr.go | 10 ++++- gdpr/impl.go | 9 +++-- gdpr/impl_test.go | 14 ++++++- 9 files changed, 95 insertions(+), 49 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index a2b28d026d7..fa9dcdc5195 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -116,10 +116,7 @@ func TestExternalCacheURLValidate(t *testing.T) { } func TestDefaults(t *testing.T) { - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + cfg, _ := newDefaultConfig(t) cmpInts(t, "port", cfg.Port, 8000) cmpInts(t, "admin_port", cfg.AdminPort, 6060) @@ -148,7 +145,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - default_value: "0" + default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +349,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) - cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "0") + cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "1") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -429,6 +426,7 @@ func TestFullConfig(t *testing.T) { func TestUnmarshalAdapterExtraInfo(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(adapterExtraInfoConfig)) cfg, err := New(v) @@ -484,14 +482,18 @@ func TestValidConfig(t *testing.T) { }, } + v := viper.New() + v.Set("gdpr.default_value", "0") + resolvedStoredRequestsConfig(&cfg) - err := cfg.validate() + err := cfg.validate(v) assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } func TestMigrateConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) migrateConfig(v) @@ -508,10 +510,7 @@ func TestMigrateConfigFromEnv(t *testing.T) { defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") } os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + cfg, _ := newDefaultConfig(t) cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) } @@ -591,6 +590,7 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidAdapterEndpointConfig)) _, err := New(v) @@ -600,6 +600,7 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) { func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidUserSyncURLConfig)) _, err := New(v) @@ -607,16 +608,16 @@ func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { } func TestNegativeRequestSize(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 - assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1") + assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1") } func TestNegativePrometheusTimeout(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Metrics.Prometheus.Port = 8001 cfg.Metrics.Prometheus.TimeoutMillisRaw = 0 - assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") + assertOneError(t, cfg.validate(v), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") } func TestInvalidHostVendorID(t *testing.T) { @@ -638,9 +639,9 @@ func TestInvalidHostVendorID(t *testing.T) { } for _, tt := range tests { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.HostVendorID = tt.vendorID - errs := cfg.validate() + errs := cfg.validate(v) assert.Equal(t, 1, len(errs), tt.description) assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) @@ -648,34 +649,47 @@ func TestInvalidHostVendorID(t *testing.T) { } func TestInvalidAMPException(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.AMPException = true - assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") + assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") } func TestInvalidGDPRDefaultValue(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.DefaultValue = "2" - assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") + assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1") +} + +func TestMissingGDPRDefaultValue(t *testing.T) { + v := viper.New() + + cfg, _ := newDefaultConfig(t) + assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") } func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: -1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds should prevent negative values, but it doesn't") } func TestOverflowedCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: (0xffff) + 1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff) } @@ -733,6 +747,7 @@ func TestNewCallsRequestValidation(t *testing.T) { for _, test := range testCases { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer([]byte( `request_validation: @@ -750,31 +765,32 @@ func TestNewCallsRequestValidation(t *testing.T) { } func TestValidateDebug(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Debug.TimeoutNotification.SamplingRate = 1.1 - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed") } func TestValidateAccountsConfigRestrictions(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Accounts.Files.Enabled = true cfg.Accounts.HTTP.Endpoint = "http://localhost" cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts" - errs := cfg.validate() + errs := cfg.validate(v) assert.Len(t, errs, 1) assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files")) } -func newDefaultConfig(t *testing.T) *Configuration { +func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") cfg, err := New(v) - assert.NoError(t, err) - return cfg + assert.NoError(t, err, "Setting up config should work but it doesn't") + return cfg, v } func assertOneError(t *testing.T, errs []error, message string) { diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 2062589e895..bdf68db5be7 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -346,6 +346,7 @@ func TestCacheVideoOnly(t *testing.T) { ctx := context.TODO() v := viper.New() config.SetupViper(v, "") + v.Set("gdpr.default_value", "0") cfg, err := config.New(v) if err != nil { t.Fatal(err.Error()) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 31988777b13..c0a1a1c9307 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1803,6 +1803,11 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] t.Fatalf("Failed to create a category Fetcher: %v", error) } + gdprDefaultValue := gdpr.SignalYes + if privacyConfig.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ adapterMap: bidderAdapters, me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), @@ -1810,7 +1815,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] cacheTime: 0, gDPR: &permissionsMock{allowAllBidders: true}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: privacyConfig.GDPR.DefaultValue, + gdprDefaultValue: gdprDefaultValue, privacyConfig: privacyConfig, categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index f7a1881f9f6..fbb844525e7 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -93,7 +93,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: "1", + gdprDefaultValue: gdpr.SignalYes, categoriesFetcher: categoriesFetcher, bidIDGenerator: &mockBidIDGenerator{false, false}, } diff --git a/exchange/utils.go b/exchange/utils.go index 4cd083a628e..7f69b341704 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - gdprDefaultValue string, + gdprDefaultValue gdpr.Signal, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == "1") + gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes) ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index dad0d69db15..5a281f9a360 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - "0", + gdpr.SignalNo, privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1639,13 +1639,18 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { Account: accountConfig, } + gdprDefaultValue := gdpr.SignalYes + if test.gdprDefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + results, privacyLabels, errs := cleanOpenRTBRequests( context.Background(), auctionReq, nil, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.gdprDefaultValue, + gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1736,7 +1741,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - "0", + gdpr.SignalNo, privacyConfig, nil) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 4179d8122ac..47d20a50899 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -39,9 +39,15 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ return &AlwaysAllow{} } + gdprDefaultValue := SignalYes + if cfg.DefaultValue == "0" { + gdprDefaultValue = SignalNo + } + permissionsImpl := &permissionsImpl{ - cfg: cfg, - vendorIDs: vendorIDs, + cfg: cfg, + gdprDefaultValue: gdprDefaultValue, + vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } diff --git a/gdpr/impl.go b/gdpr/impl.go index 7fa35aa7d38..3f2ec398f96 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -28,9 +28,10 @@ const ( ) type permissionsImpl struct { - cfg config.GDPR - vendorIDs map[openrtb_ext.BidderName]uint16 - fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) + cfg config.GDPR + gdprDefaultValue Signal + vendorIDs map[openrtb_ext.BidderName]uint16 + fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { @@ -94,7 +95,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.DefaultValue == "0" { + if p.gdprDefaultValue == SignalNo { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 345dd52621d..f4b0392876b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -21,7 +21,8 @@ func TestDisallowOnEmptyConsent(t *testing.T) { HostVendorID: 3, DefaultValue: "0", }, - vendorIDs: nil, + gdprDefaultValue: SignalNo, + vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: failedListFetcher, }, @@ -303,6 +304,11 @@ func TestAllowActivities(t *testing.T) { for _, tt := range tests { perms.cfg.DefaultValue = tt.gdprDefaultValue + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -719,6 +725,12 @@ func TestNormalizeGDPR(t *testing.T) { }, } + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } + normalizedSignal := perms.normalizeGDPR(tt.giveSignal) assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) From e1dd3fca4d567626a6eb3c80d1b21dce9354257f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:33:10 +0200 Subject: [PATCH 583/603] New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 --- adapters/smilewanted/params_test.go | 58 ++++++++++ adapters/smilewanted/smilewanted.go | 106 ++++++++++++++++++ adapters/smilewanted/smilewanted_test.go | 20 ++++ .../exemplary/simple-banner.json | 94 ++++++++++++++++ .../exemplary/simple-video.json | 87 ++++++++++++++ .../smilewantedtest/params/race/banner.json | 3 + .../smilewantedtest/params/race/video.json | 3 + .../supplemental/bad-server-response.json | 63 +++++++++++ .../supplemental/status-code-204.json | 59 ++++++++++ .../supplemental/status-code-400.json | 64 +++++++++++ .../supplemental/unexpected-status-code.json | 64 +++++++++++ adapters/smilewanted/usersync.go | 12 ++ adapters/smilewanted/usersync_test.go | 34 ++++++ config/config.go | 2 + static/bidder-info/smilewanted.yaml | 12 ++ static/bidder-params/smilewanted.json | 14 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 18 files changed, 698 insertions(+) create mode 100644 adapters/smilewanted/params_test.go create mode 100644 adapters/smilewanted/smilewanted.go create mode 100644 adapters/smilewanted/smilewanted_test.go create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-video.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/banner.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/video.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json create mode 100644 adapters/smilewanted/usersync.go create mode 100644 adapters/smilewanted/usersync_test.go create mode 100644 static/bidder-info/smilewanted.yaml create mode 100644 static/bidder-params/smilewanted.json diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go new file mode 100644 index 00000000000..2ea032d6ff3 --- /dev/null +++ b/adapters/smilewanted/params_test.go @@ -0,0 +1,58 @@ +package smilewanted + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/smilewanted.json +// +// These also validate the format of the external API: request.imp[i].ext.smilewanted + +// TestValidParams makes sure that the smilewanted schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmileWanted, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected SmileWanted params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the SmileWanted schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmileWanted, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneId": "zone_code"}`, +} + +var invalidParams = []string{ + `{"zoneId": 100}`, + `{"zoneId": true}`, + `{"zoneId": 123}`, + `{"zoneID": "1"}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go new file mode 100644 index 00000000000..376389df787 --- /dev/null +++ b/adapters/smilewanted/smilewanted.go @@ -0,0 +1,106 @@ +package smilewanted + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + URI string +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + request.AT = 1 //Defaulting to first price auction for all prebid requests + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Json not encoded. err: %s", err), + }} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + headers.Add("sw-integration-type", "prebid_server") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }}, []error{} +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %s.", err), + }} + } + + var bidReq openrtb2.BidRequest + if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + }) + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for. +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner //default type + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType + } + } + return mediaType +} + +// Builder builds a new instance of the SmileWanted adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go new file mode 100644 index 00000000000..75e7849e750 --- /dev/null +++ b/adapters/smilewanted/smilewanted_test.go @@ -0,0 +1,20 @@ +package smilewanted + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSmileWanted, config.Adapter{ + Endpoint: "http://example.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "smilewantedtest", bidder) +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..0c68d74c588 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "smilewanted", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b3ff9ba9edd --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "zoneId": "zone_code_test_video" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_video" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "smilewanted", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/banner.json b/adapters/smilewanted/smilewantedtest/params/race/banner.json new file mode 100644 index 00000000000..42dddd702a0 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_display" +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/video.json b/adapters/smilewanted/smilewantedtest/params/race/video.json new file mode 100644 index 00000000000..64ac780ecde --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_video" +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..461ad9327a9 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "bad_json" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json new file mode 100644 index 00000000000..0d8a432e26d --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json new file mode 100644 index 00000000000..bdf2caa3c01 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..49a11e3ead3 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/usersync.go b/adapters/smilewanted/usersync.go new file mode 100644 index 00000000000..8f29cb845d8 --- /dev/null +++ b/adapters/smilewanted/usersync.go @@ -0,0 +1,12 @@ +package smilewanted + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmileWantedSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smilewanted", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smilewanted/usersync_test.go b/adapters/smilewanted/usersync_test.go new file mode 100644 index 00000000000..497e5061554 --- /dev/null +++ b/adapters/smilewanted/usersync_test.go @@ -0,0 +1,34 @@ +package smilewanted + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSmileWantedSyncer(t *testing.T) { + syncURL := "//csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSmileWantedSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA", + }, + CCPA: ccpa.Policy{ + Consent: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//csync.smilewanted.com/getuid?source=prebid-server&gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 0f81c1f16a2..3b0d83787a6 100644 --- a/config/config.go +++ b/config/config.go @@ -646,6 +646,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartAdserver, "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartyAds, "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmileWanted, "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -921,6 +922,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.smartyads.endpoint", "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}") + v.SetDefault("adapters.smilewanted.endpoint", "http://prebid-server.smilewanted.com") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml new file mode 100644 index 00000000000..81b0585bb5e --- /dev/null +++ b/static/bidder-info/smilewanted.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "tech@smilewanted.com" +gvlVendorID: 639 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/smilewanted.json b/static/bidder-params/smilewanted.json new file mode 100644 index 00000000000..be4f9bc142d --- /dev/null +++ b/static/bidder-params/smilewanted.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SmileWanted Adapter Params", + "description": "A schema which validates params accepted by the SmileWanted adapter", + "type": "object", + "properties": { + "zoneId": { + "type": "string", + "description": "An ID which identifies the SmileWanted zone code", + "minLength": 1 + } + }, + "required": ["zoneId"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 88752f4d7d7..169417e07e8 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -73,6 +73,7 @@ import ( "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -174,6 +175,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index b84947c53e9..3f828447bc2 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -82,6 +82,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSmartAdserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSmartyAds): syncConfig, + string(openrtb_ext.BidderSmileWanted): syncConfig, string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, From 2507800004488879fa661b01ec2a6468888644dc Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 17 Jun 2021 11:36:31 -0400 Subject: [PATCH 584/603] Fix a weak vendor enforcement bug where vendor does not exist (#1890) --- gdpr/impl_test.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f4b0392876b..d1a6cad75e8 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,10 +382,11 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAudienceNetwork: 55, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ @@ -414,6 +415,15 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { passID: true, weakVendorEnforcement: true, }, + { + description: "Unknown vendor test, insufficient purposes claimed, basic enforcement", + bidder: openrtb_ext.BidderAudienceNetwork, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowBid: true, + passGeo: true, + passID: true, + weakVendorEnforcement: true, + }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, From b81e8dfafd3c33b9889118ae42d51e64db3cbd60 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Sat, 19 Jun 2021 16:37:48 -0400 Subject: [PATCH 585/603] Update To Go 1.16 (#1888) --- .devcontainer/devcontainer.json | 4 ++-- .github/workflows/validate-merge.yml | 2 +- .github/workflows/validate.yml | 2 +- Dockerfile | 4 ++-- README.md | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b2c53776ad4..bbb76d3675c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,8 +5,8 @@ "build": { "dockerfile": "Dockerfile", "args": { - // Update the VARIANT arg to pick a version of Go: 1, 1.15, 1.14 - "VARIANT": "1.14", + // Update the VARIANT arg to pick a version of Go + "VARIANT": "1.16", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 30370178ca8..9cf371ca168 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -12,7 +12,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.14.2 + go-version: 1.16.4 - name: Checkout Merged Branch uses: actions/checkout@v2 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3efc51d287a..1eb137467ec 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,7 +10,7 @@ jobs: validate: strategy: matrix: - go-version: [1.14.x, 1.15.x] + go-version: [1.15.x, 1.16.x] os: [ubuntu-18.04] runs-on: ${{ matrix.os }} diff --git a/Dockerfile b/Dockerfile index d76398dc6d3..defb64c8586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget RUN cd /tmp && \ - wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \ - tar -xf go1.14.2.linux-amd64.tar.gz && \ + wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \ + tar -xf go1.16.4.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ diff --git a/README.md b/README.md index 8f938d66918..bc100c67340 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid- ## Installation -First install [Go](https://golang.org/doc/install) version 1.14 or newer. +First install [Go](https://golang.org/doc/install) version 1.15 or newer. Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. From 1ec4f65b096e3b5d507af2c50889a7b8cc38fa9a Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 22 Jun 2021 20:53:25 -0400 Subject: [PATCH 586/603] Friendlier Startup Error Messages (#1894) --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 294cc141169..71b7ddaa90a 100644 --- a/main.go +++ b/main.go @@ -37,12 +37,12 @@ func main() { cfg, err := loadConfig() if err != nil { - glog.Fatalf("Configuration could not be loaded or did not pass validation: %v", err) + glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } err = serve(Rev, cfg) if err != nil { - glog.Errorf("prebid-server failed: %v", err) + glog.Exitf("prebid-server failed: %v", err) } } */ From 3487053e7760a77d4bf13d9f1e29a050865dbed7 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 23 Jun 2021 12:51:40 -0400 Subject: [PATCH 587/603] Second fix for weak vendor enforcement (#1896) --- gdpr/impl.go | 2 ++ gdpr/impl_test.go | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index 3f2ec398f96..af3aa66b596 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -81,6 +81,8 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, if id, ok := p.vendorIDs[bidder]; ok { return p.allowActivities(ctx, id, consent, weakVendorEnforcement) + } else if weakVendorEnforcement { + return p.allowActivities(ctx, 0, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d1a6cad75e8..cde485467b3 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,11 +382,10 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, - openrtb_ext.BidderAudienceNetwork: 55, + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ From ae2dc1e32c754060319f3e634c966dc2fbb9f050 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 24 Jun 2021 19:51:35 +0300 Subject: [PATCH 588/603] Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 15 ++++++ adapters/rubicon/rubicon_test.go | 46 +++++++++++++++++++ .../rubicontest/exemplary/simple-video.json | 4 ++ 3 files changed, 65 insertions(+) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 89d69522fe8..73f6a5d39ca 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -750,6 +750,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } + resolvedBidFloor, resolvedBidFloorCur := resolveBidFloorAttributes(thisImp.BidFloor, thisImp.BidFloorCur) + thisImp.BidFloorCur = resolvedBidFloorCur + thisImp.BidFloor = resolvedBidFloor + if request.User != nil { userCopy := *request.User userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} @@ -893,6 +897,17 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } +// Will be replaced after https://github.com/prebid/prebid-server/issues/1482 resolution +func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, string) { + if bidFloor > 0 { + if strings.ToUpper(bidFloorCur) == "EUR" { + return bidFloor * 1.2, "USD" + } + } + + return bidFloor, bidFloorCur +} + func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { var segmentIdsToCopy = make([]string, 0) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index dc5b3a90423..8f8d3fb1557 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -572,6 +572,52 @@ func TestResolveVideoSizeId(t *testing.T) { } } +func TestResolveBidFloorAttributes(t *testing.T) { + testScenarios := []struct { + bidFloor float64 + bidFloorCur string + expectedBidFloor float64 + expectedBidFloorCur string + }{ + { + bidFloor: 1, + bidFloorCur: "EUR", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 1, + bidFloorCur: "Eur", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 0, + bidFloorCur: "EUR", + expectedBidFloor: 0, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: -1, + bidFloorCur: "EUR", + expectedBidFloor: -1, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: 1, + bidFloorCur: "USD", + expectedBidFloor: 1, + expectedBidFloorCur: "USD", + }, + } + + for _, scenario := range testScenarios { + bidFloor, bidFloorCur := resolveBidFloorAttributes(scenario.bidFloor, scenario.bidFloorCur) + assert.Equal(t, scenario.expectedBidFloor, bidFloor) + assert.Equal(t, scenario.expectedBidFloorCur, bidFloorCur) + } +} + func TestNoContentResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index b85c28def44..a90670e53be 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -71,6 +71,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1, + "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -192,6 +194,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1.2, + "bidfloorcur": "USD", "ext": { "rp": { "track": { From 64b473dea9673897977fb5129cd74b0826c7733f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 24 Jun 2021 19:26:58 +0200 Subject: [PATCH 589/603] Outbrain adapter: overwrite tagid only if it exists (#1895) --- adapters/outbrain/outbrain.go | 6 +- .../supplemental/general_params.json | 139 ++++++++++++++++++ .../supplemental/optional_params.json | 3 + 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 adapters/outbrain/outbraintest/supplemental/general_params.json diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index 282a6d53aa0..6b121cb4732 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -43,8 +43,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errs = append(errs, err) continue } - imp.TagID = outbrainExt.TagId - reqCopy.Imp[i] = imp + if outbrainExt.TagId != "" { + imp.TagID = outbrainExt.TagId + reqCopy.Imp[i] = imp + } } publisher := &openrtb2.Publisher{ diff --git a/adapters/outbrain/outbraintest/supplemental/general_params.json b/adapters/outbrain/outbraintest/supplemental/general_params.json new file mode 100644 index 00000000000..b2a547c8b4e --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/general_params.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json index a69ceaa0c85..d75875e0e70 100644 --- a/adapters/outbrain/outbraintest/supplemental/optional_params.json +++ b/adapters/outbrain/outbraintest/supplemental/optional_params.json @@ -12,6 +12,7 @@ } ] }, + "tagid": "should-be-overwritten-tagid", "ext": { "bidder": { "publisher": { @@ -26,6 +27,8 @@ } } ], + "bcat": ["should-be-overwritten-bcat"], + "badv": ["should-be-overwritten-badv"], "site": { "page": "http://example.com" }, From a96f485ae130471628808b6dead3bf3fd75f87b1 Mon Sep 17 00:00:00 2001 From: bidmyadz <82382704+bidmyadz@users.noreply.github.com> Date: Wed, 30 Jun 2021 18:02:31 +0300 Subject: [PATCH 590/603] New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz --- adapters/bidmyadz/bidmyadz.go | 157 +++++++++++++++++ adapters/bidmyadz/bidmyadz_test.go | 18 ++ .../bidmyadztest/exemplary/banner.json | 146 ++++++++++++++++ .../bidmyadztest/exemplary/native.json | 141 +++++++++++++++ .../bidmyadztest/exemplary/video.json | 160 ++++++++++++++++++ .../bidmyadztest/params/race/banner.json | 3 + .../bidmyadztest/params/race/native.json | 3 + .../bidmyadztest/params/race/video.json | 3 + .../supplemental/invalid-device-fields.json | 48 ++++++ .../supplemental/invalid-multi-imps.json | 61 +++++++ .../supplemental/missing-mediatype.json | 122 +++++++++++++ .../supplemental/response-without-bids.json | 109 ++++++++++++ .../response-without-seatbid.json | 106 ++++++++++++ .../bidmyadztest/supplemental/status-204.json | 94 ++++++++++ .../bidmyadztest/supplemental/status-400.json | 101 +++++++++++ .../status-service-unavailable.json | 100 +++++++++++ .../supplemental/status-unknown.json | 101 +++++++++++ adapters/bidmyadz/params_test.go | 49 ++++++ adapters/bidmyadz/usersync.go | 12 ++ adapters/bidmyadz/usersync_test.go | 33 ++++ config/config.go | 2 + static/bidder-info/bidmyadz.yaml | 13 ++ static/bidder-params/bidmyadz.json | 12 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1597 insertions(+) create mode 100644 adapters/bidmyadz/bidmyadz.go create mode 100644 adapters/bidmyadz/bidmyadz_test.go create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-204.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-400.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json create mode 100644 adapters/bidmyadz/params_test.go create mode 100644 adapters/bidmyadz/usersync.go create mode 100644 adapters/bidmyadz/usersync_test.go create mode 100644 static/bidder-info/bidmyadz.yaml create mode 100644 static/bidder-params/bidmyadz.json diff --git a/adapters/bidmyadz/bidmyadz.go b/adapters/bidmyadz/bidmyadz.go new file mode 100644 index 00000000000..829d57e606f --- /dev/null +++ b/adapters/bidmyadz/bidmyadz.go @@ -0,0 +1,157 @@ +package bidmyadz + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + var errors []error + + if len(openRTBRequest.Imp) > 1 { + errors = append(errors, &errortypes.BadInput{ + Message: "Bidder does not support multi impression", + }) + } + + if openRTBRequest.Device.IP == "" && openRTBRequest.Device.IPv6 == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "IP/IPv6 is a required field", + }) + } + + if openRTBRequest.Device.UA == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "User-Agent is a required field", + }) + } + + if len(errors) != 0 { + return nil, errors + } + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.endpoint, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bidder is unavailable. Please contact your account manager.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong. Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid.Bids", + }} + } + + bid := bids[0] + + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("BidExt parsing error. %s", err.Error()), + }} + } + + bidType, err := getBidType(bidExt) + + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + return bidResponse, nil +} + +func getBidType(ext bidExt) (openrtb_ext.BidType, error) { + return openrtb_ext.ParseBidType(ext.MediaType) +} diff --git a/adapters/bidmyadz/bidmyadz_test.go b/adapters/bidmyadz/bidmyadz_test.go new file mode 100644 index 00000000000..b0de6a02956 --- /dev/null +++ b/adapters/bidmyadz/bidmyadz_test.go @@ -0,0 +1,18 @@ +package bidmyadz + +import ( + "github.com/stretchr/testify/assert" + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderBidmyadz, config.Adapter{ + Endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "bidmyadztest", bidder) +} diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/banner.json b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json new file mode 100644 index 00000000000..460291bc4f0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/native.json b/adapters/bidmyadz/bidmyadztest/exemplary/native.json new file mode 100644 index 00000000000..984802601c0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/native.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/video.json b/adapters/bidmyadz/bidmyadztest/exemplary/video.json new file mode 100644 index 00000000000..92c42f8331a --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/video.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/banner.json b/adapters/bidmyadz/bidmyadztest/params/race/banner.json new file mode 100644 index 00000000000..18dce42f2c4 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "placementId": "tbanner" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/native.json b/adapters/bidmyadz/bidmyadztest/params/race/native.json new file mode 100644 index 00000000000..0600af3a894 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "placementId": "tnative" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/video.json b/adapters/bidmyadz/bidmyadztest/params/race/video.json new file mode 100644 index 00000000000..85478bf22c5 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "placementId": "tvideo" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json new file mode 100644 index 00000000000..d620a050632 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "IP/IPv6 is a required field", + "comparison": "literal" + }, { + "value": "User-Agent is a required field", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json new file mode 100644 index 00000000000..09020fc89e9 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }, { + "id": "1", + "secure": 1, + "bidfloor": 0.11, + "bidfloorcur": "USD", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "placementId": "3234" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "Bidder does not support multi impression", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json new file mode 100644 index 00000000000..486d7324dfc --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "BidExt parsing error. unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json new file mode 100644 index 00000000000..fe3361f69d8 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid.Bids", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json new file mode 100644 index 00000000000..727e745e762 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json new file mode 100644 index 00000000000..05efda0e9f3 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json new file mode 100644 index 00000000000..e24acdc6766 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "Source blocked" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"Source blocked\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json new file mode 100644 index 00000000000..13e22f55889 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bidder is unavailable. Please contact your account manager.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json new file mode 100644 index 00000000000..69b649e19ad --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Forbidden" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong. Status Code: [ 403 ] \"Forbidden\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/params_test.go b/adapters/bidmyadz/params_test.go new file mode 100644 index 00000000000..857cde86d22 --- /dev/null +++ b/adapters/bidmyadz/params_test.go @@ -0,0 +1,49 @@ +package bidmyadz + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "placementId": "1234" }`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderBidmyadz, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected bidmyadz params: %s", validParam) + } + } +} + +var invalidParams = []string{ + `1234`, + ``, + `true`, + `null`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "placementId": null }`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderBidmyadz, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/bidmyadz/usersync.go b/adapters/bidmyadz/usersync.go new file mode 100644 index 00000000000..755a184d6e4 --- /dev/null +++ b/adapters/bidmyadz/usersync.go @@ -0,0 +1,12 @@ +package bidmyadz + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewBidmyadzSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("bidmyadz", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/bidmyadz/usersync_test.go b/adapters/bidmyadz/usersync_test.go new file mode 100644 index 00000000000..11b5fedd73f --- /dev/null +++ b/adapters/bidmyadz/usersync_test.go @@ -0,0 +1,33 @@ +package bidmyadz + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewBidmyadzSyncer(t *testing.T) { + syncURL := "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewBidmyadzSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 3b0d83787a6..963ead021fb 100644 --- a/config/config.go +++ b/config/config.go @@ -597,6 +597,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBidmyadz, "https://cookie-sync.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&red="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbidmyadz%26uid%3D%5BUID%5D%26us_privacy%3D{{.USPrivacy}}%26gdpr_consent%3D{{.GDPRConsent}}%26gdpr%3D{{.GDPR}}") // openrtb_ext.BidderBidsCube doesn't have a good default. // openrtb_ext.BidderBmtm doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -857,6 +858,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}") v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io") + v.SetDefault("adapters.bidmyadz.endpoint", "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc") v.SetDefault("adapters.bidscube.endpoint", "http://supply.bidscube.com/?c=o&m=rtb") v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml new file mode 100644 index 00000000000..70a995a2798 --- /dev/null +++ b/static/bidder-info/bidmyadz.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "contact@bidmyadz.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/bidmyadz.json b/static/bidder-params/bidmyadz.json new file mode 100644 index 00000000000..4e7b1119e08 --- /dev/null +++ b/static/bidder-params/bidmyadz.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BidMyAdz Adapter Params", + "description": "A schema which validates params accepted by the BidMyAdz adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string" + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 169417e07e8..7de59491e02 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -28,6 +28,7 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/bidmyadz" "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" @@ -128,6 +129,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBmtm, bmtm.NewBmtmSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderBidmyadz, bidmyadz.NewBidmyadzSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 3f828447bc2..3029de9fd8a 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -37,6 +37,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBeachfront): syncConfig, string(openrtb_ext.BidderBeintoo): syncConfig, string(openrtb_ext.BidderBetween): syncConfig, + string(openrtb_ext.BidderBidmyadz): syncConfig, string(openrtb_ext.BidderBmtm): syncConfig, string(openrtb_ext.BidderBrightroll): syncConfig, string(openrtb_ext.BidderColossus): syncConfig, From 2d53a0e117bc1985367243d6adea9f75578a3f00 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 30 Jun 2021 12:54:40 -0400 Subject: [PATCH 591/603] Currency Conversion Utility Function (#1901) --- adapters/bidder.go | 20 ++++++++ adapters/bidder_test.go | 63 ++++++++++++++++++++++++++ currency/aggregate_conversions.go | 2 +- currency/aggregate_conversions_test.go | 2 +- currency/constant_rates.go | 2 +- currency/errors.go | 6 +-- currency/rates.go | 6 +-- exchange/bidder_test.go | 20 ++++---- exchange/exchange.go | 2 +- 9 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 adapters/bidder_test.go diff --git a/adapters/bidder.go b/adapters/bidder.go index c277ee5b466..f001320f2e2 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -7,6 +7,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -145,6 +146,25 @@ func (r *RequestData) SetBasicAuth(username string, password string) { type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string + currencyConversions currency.Conversions +} + +func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { + return ExtraRequestInfo{ + currencyConversions: c, + } +} + +// ConvertCurrency converts a given amount from one currency to another, or returns: +// - Error if the `from` or `to` arguments are malformed or unknown ISO-4217 codes. +// - ConversionNotFoundError if the conversion mapping is unknown to Prebid Server +// and not provided in the bid request. +func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float64, error) { + if rate, err := r.currencyConversions.GetRate(from, to); err == nil { + return value * rate, nil + } else { + return 0, err + } } type Builder func(openrtb_ext.BidderName, config.Adapter) (Bidder, error) diff --git a/adapters/bidder_test.go b/adapters/bidder_test.go new file mode 100644 index 00000000000..f0b833cec2e --- /dev/null +++ b/adapters/bidder_test.go @@ -0,0 +1,63 @@ +package adapters + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestExtraRequestInfoConvertCurrency(t *testing.T) { + var ( + givenValue float64 = 2 + givenFrom string = "AAA" + givenTo string = "BBB" + ) + + testCases := []struct { + description string + setMock func(m *mock.Mock) + expectedValue float64 + expectedError error + }{ + { + description: "Success", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, nil) }, + expectedValue: 5, + expectedError: nil, + }, + { + description: "Error", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, errors.New("some error")) }, + expectedValue: 0, + expectedError: errors.New("some error"), + }, + } + + for _, test := range testCases { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + + extraRequestInfo := NewExtraRequestInfo(mockConversions) + result, err := extraRequestInfo.ConvertCurrency(givenValue, givenFrom, givenTo) + + mockConversions.AssertExpectations(t) + assert.Equal(t, test.expectedValue, result, test.description+":result") + assert.Equal(t, test.expectedError, err, test.description+":err") + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go index 53c5ebff4b6..a15404fe501 100644 --- a/currency/aggregate_conversions.go +++ b/currency/aggregate_conversions.go @@ -23,7 +23,7 @@ func (re *AggregateConversions) GetRate(from string, to string) (float64, error) rate, err := re.customRates.GetRate(from, to) if err == nil { return rate, nil - } else if _, isMissingRateErr := err.(ConversionRateNotFound); !isMissingRateErr { + } else if _, isMissingRateErr := err.(ConversionNotFoundError); !isMissingRateErr { // other error, return the error return 0, err } diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go index 35ca51a1fe7..773a596c28c 100644 --- a/currency/aggregate_conversions_test.go +++ b/currency/aggregate_conversions_test.go @@ -65,7 +65,7 @@ func TestGroupedGetRate(t *testing.T) { }, }, { - expectedError: ConversionRateNotFound{"GBP", "EUR"}, + expectedError: ConversionNotFoundError{FromCur: "GBP", ToCur: "EUR"}, testCases: []aTest{ {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, }, diff --git a/currency/constant_rates.go b/currency/constant_rates.go index dde317d809e..ccc5c24d3fc 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -27,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go index d764c15b984..bb4c42aa90a 100644 --- a/currency/errors.go +++ b/currency/errors.go @@ -2,12 +2,12 @@ package currency import "fmt" -// ConversionRateNotFound is thrown by the currency.Conversions GetRate(from string, to string) method +// ConversionNotFoundError is thrown by the currency.Conversions GetRate(from string, to string) method // when the conversion rate between the two currencies, nor its reciprocal, can be found. -type ConversionRateNotFound struct { +type ConversionNotFoundError struct { FromCur, ToCur string } -func (err ConversionRateNotFound) Error() string { +func (err ConversionNotFoundError) Error() string { return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur) } diff --git a/currency/rates.go b/currency/rates.go index 62914c4b2e2..b9cb0201b38 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -47,9 +47,9 @@ func (r *Rates) UnmarshalJSON(b []byte) error { // GetRate returns the conversion rate between two currencies or: // - An error if one of the currency strings is not well-formed // - An error if any of the currency strings is not a recognized currency code. -// - A MissingConversionRate error in case the conversion rate between the two +// - A ConversionNotFoundError in case the conversion rate between the two // given currencies is not in the currencies rates map -func (r *Rates) GetRate(from string, to string) (float64, error) { +func (r *Rates) GetRate(from, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) if err != nil { @@ -70,7 +70,7 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { // In case we have an entry TO -> FROM return 1 / conversion, nil } - return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 39a5b992ea9..87e1a8d8366 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"JPY", "USD"}, + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -587,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"JPY", "USD"}, - currency.ConversionRateNotFound{"BZD", "USD"}, - currency.ConversionRateNotFound{"DKK", "USD"}, + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "BZD", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "DKK", ToCur: "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -720,9 +720,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -754,7 +754,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -762,7 +762,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"GBP", "USD"}, + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -770,7 +770,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"GBP", "USD"}, + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index c5a747766e9..d670c504db7 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -444,7 +444,7 @@ func (e *exchange) getAllBids( if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok { adjustmentFactor = givenAdjustment } - var reqInfo adapters.ExtraRequestInfo + reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) From d6ae71f72be47167a70d938833151f70d1e3724d Mon Sep 17 00:00:00 2001 From: lunamedia <73552749+lunamedia@users.noreply.github.com> Date: Wed, 30 Jun 2021 23:44:01 +0300 Subject: [PATCH 592/603] New Adapter: SA Lunamedia (#1891) --- adapters/sa_lunamedia/params_test.go | 52 ++++++ adapters/sa_lunamedia/salunamedia.go | 132 ++++++++++++++ adapters/sa_lunamedia/salunamedia_test.go | 18 ++ .../salunamediatest/exemplary/banner.json | 142 +++++++++++++++ .../salunamediatest/exemplary/native.json | 132 ++++++++++++++ .../salunamediatest/exemplary/video.json | 169 ++++++++++++++++++ .../salunamediatest/params/race/banner.json | 3 + .../salunamediatest/params/race/native.json | 3 + .../salunamediatest/params/race/video.json | 3 + .../supplemental/bad-response.json | 98 ++++++++++ .../supplemental/empty-seatbid.json | 102 +++++++++++ .../supplemental/status-204.json | 92 ++++++++++ .../supplemental/status-400.json | 99 ++++++++++ .../supplemental/status-503.json | 98 ++++++++++ .../supplemental/unexpected-status.json | 99 ++++++++++ adapters/sa_lunamedia/usersync.go | 12 ++ adapters/sa_lunamedia/usersync_test.go | 33 ++++ config/config.go | 2 + openrtb_ext/imp_sa_lunamedia.go | 6 + static/bidder-info/sa_lunamedia.yaml | 14 ++ static/bidder-params/sa_lunamedia.json | 17 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 23 files changed, 1329 insertions(+) create mode 100644 adapters/sa_lunamedia/params_test.go create mode 100644 adapters/sa_lunamedia/salunamedia.go create mode 100644 adapters/sa_lunamedia/salunamedia_test.go create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json create mode 100644 adapters/sa_lunamedia/usersync.go create mode 100644 adapters/sa_lunamedia/usersync_test.go create mode 100644 openrtb_ext/imp_sa_lunamedia.go create mode 100644 static/bidder-info/sa_lunamedia.yaml create mode 100644 static/bidder-params/sa_lunamedia.json diff --git a/adapters/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go new file mode 100644 index 00000000000..bf7a1f493e6 --- /dev/null +++ b/adapters/sa_lunamedia/params_test.go @@ -0,0 +1,52 @@ +package salunamedia + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "2", "type": "network"}`, + `{ "key": "1"}`, + `{ "key": "33232", "type": "publisher"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSaLunaMedia, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected sa_lunamedia params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "type": "network" }`, + `{ "key": "asddsfd", "type": "any"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSaLunaMedia, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go new file mode 100644 index 00000000000..ea6e12b01d6 --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia.go @@ -0,0 +1,132 @@ +package salunamedia + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.endpoint, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder unavailable. Please contact the bidder support.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid.Bids", + }} + } + + bid := bids[0] + + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Missing BidExt", + }} + } + + bidType, err := getBidType(bidExt) + + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + return bidResponse, nil +} + +func getBidType(ext bidExt) (openrtb_ext.BidType, error) { + return openrtb_ext.ParseBidType(ext.MediaType) +} diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go new file mode 100644 index 00000000000..f5d2058208e --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia_test.go @@ -0,0 +1,18 @@ +package salunamedia + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSaLunaMedia, config.Adapter{ + Endpoint: "http://test.com/pserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "salunamediatest", bidder) +} diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json new file mode 100644 index 00000000000..2ce4ad81106 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/native.json b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json new file mode 100644 index 00000000000..74d8940f0a1 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "native" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/video.json b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json new file mode 100644 index 00000000000..9a042d726d9 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 3, + "maxduration": 3000, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [2], + "pos": 0 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "key": "test" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [{ + "bid": [{ + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "video" + } + }], + "seat": "seat" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/native.json b/adapters/sa_lunamedia/salunamediatest/params/race/native.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/video.json b/adapters/sa_lunamedia/salunamediatest/params/race/video.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json new file mode 100644 index 00000000000..6373207d481 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..8942b3be65a --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json new file mode 100644 index 00000000000..042b96bde65 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json new file mode 100644 index 00000000000..1ecdc46e5fa --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json new file mode 100644 index 00000000000..2590418a75f --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bidder unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..a54737cafdb --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Access is denied" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Status Code: [ 403 ] \"Access is denied\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/usersync.go b/adapters/sa_lunamedia/usersync.go new file mode 100644 index 00000000000..f78b7944cb2 --- /dev/null +++ b/adapters/sa_lunamedia/usersync.go @@ -0,0 +1,12 @@ +package salunamedia + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSaLunamediaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("salunamedia", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/sa_lunamedia/usersync_test.go b/adapters/sa_lunamedia/usersync_test.go new file mode 100644 index 00000000000..e3820fbc1af --- /dev/null +++ b/adapters/sa_lunamedia/usersync_test.go @@ -0,0 +1,33 @@ +package salunamedia + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewSaLunamediaSyncer(t *testing.T) { + syncURL := "https://test.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewSaLunamediaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://test.com/pserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 963ead021fb..35545c38c6f 100644 --- a/config/config.go +++ b/config/config.go @@ -628,6 +628,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSaLunaMedia, "https://cookie.lmgssp.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsa_lunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderMadvertise doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderMediafuse doesn't have a good default. @@ -895,6 +896,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver") v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") diff --git a/openrtb_ext/imp_sa_lunamedia.go b/openrtb_ext/imp_sa_lunamedia.go new file mode 100644 index 00000000000..cb99b0ac561 --- /dev/null +++ b/openrtb_ext/imp_sa_lunamedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpSaLunamedia struct { + Key string `json:"key"` + Type string `json:"type,omitempty"` +} diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml new file mode 100644 index 00000000000..181e1fd6c73 --- /dev/null +++ b/static/bidder-info/sa_lunamedia.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@lunamedia.io" +gvlVendorID: 998 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/sa_lunamedia.json b/static/bidder-params/sa_lunamedia.json new file mode 100644 index 00000000000..51ca09098e2 --- /dev/null +++ b/static/bidder-params/sa_lunamedia.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sa_Lunamedia Adapter Params", + "description": "A schema which validates params accepted by the Sa_Lunamedia adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + }, + "type": { + "type": "string", + "enum": ["network", "publisher"] + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7de59491e02..7472bf3a70d 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -70,6 +70,7 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" @@ -156,6 +157,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSaLunaMedia, salunamedia.NewSaLunamediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMediafuse, mediafuse.NewMediafuseSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 3029de9fd8a..b71d481df93 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -65,6 +65,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, + string(openrtb_ext.BidderSaLunaMedia): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMediafuse): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, From 3b35882aaa0429db4f996799df9d32653a74d37e Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 1 Jul 2021 08:49:34 -0700 Subject: [PATCH 593/603] Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy --- .../adformtest/supplemental/user-nil.json | 7 +- adapters/dmx/dmx.go | 2 +- adapters/dmx/dmx_test.go | 77 +++++-------------- adapters/rubicon/rubicon.go | 14 ++-- adapters/rubicon/rubicon_test.go | 10 +-- .../rubicontest/exemplary/simple-video.json | 1 - endpoints/openrtb2/amp_auction_test.go | 12 +-- endpoints/openrtb2/auction.go | 6 +- .../invalid-whole/digitrust.json | 46 ----------- .../valid-whole/supplementary/digitrust.json | 50 ------------ exchange/exchange_test.go | 4 +- .../exchangetest/request-other-user-ext.json | 14 +--- .../exchangetest/request-user-no-prebid.json | 10 --- exchange/utils.go | 2 +- exchange/utils_test.go | 4 +- openrtb_ext/user.go | 13 ---- privacy/scrubber.go | 5 +- privacy/scrubber_test.go | 35 +++------ 18 files changed, 46 insertions(+), 266 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json delete mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json index 5f02fe85971..96ea1dbff71 100644 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ b/adapters/adform/adformtest/supplemental/user-nil.json @@ -31,12 +31,7 @@ }, "user": { "ext": { - "consent": "abc2", - "digitrust": { - "ID": "digitrustId", - "KeyV": 1, - "Pref": 0 - } + "consent": "abc2" } } }, diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 7124d229347..adcec4a33c5 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -148,7 +148,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt } if dmxReq.User.Ext != nil { if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil { - if len(userExt.Eids) > 0 || (userExt.DigiTrust != nil && userExt.DigiTrust.ID != "") { + if len(userExt.Eids) > 0 { anyHasId = true } } diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index a9f1e8bbc79..409290c110d 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -643,50 +643,6 @@ func TestUserEidsOnly(t *testing.T) { } } -func TestUserDigitrustOnly(t *testing.T) { - var w, h int = 300, 250 - - var width, height int64 = int64(w), int64(h) - - bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - imp1 := openrtb2.Imp{ - ID: "imp1", - Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb2.Banner{ - W: &width, - H: &height, - Format: []openrtb2.Format{ - {W: 300, H: 250}, - }, - }} - - inputRequest := openrtb2.BidRequest{ - Imp: []openrtb2.Imp{imp1, imp1, imp1}, - Site: &openrtb2.Site{ - Publisher: &openrtb2.Publisher{ - ID: "10007", - }, - }, - User: &openrtb2.User{Ext: json.RawMessage(`{ - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, - ID: "1234", - } - - actualAdapterRequests, _ := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) - if len(actualAdapterRequests) != 1 { - t.Errorf("should have 1 request") - } -} - func TestUsersEids(t *testing.T) { var w, h int = 300, 250 @@ -725,53 +681,56 @@ func TestUsersEids(t *testing.T) { "rtiPartner": "TDID" } }] - },{ + }, + { "source": "pubcid.org", "uids": [{ - "id":"11111111" + "id": "11111111" }] }, - { + { "source": "id5-sync.com", "uids": [{ "id": "ID5-12345" }] - }, - { + }, + { "source": "parrable.com", "uids": [{ "id": "01.1563917337.test-eid" }] - },{ + }, + { "source": "identityLink", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "criteo", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "britepool.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "liveintent.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "netid.de", "uids": [{ "id": "11111111" }] - }], - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, + }] + }`)}, ID: "1234", } diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 73f6a5d39ca..84d8596449c 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -96,12 +96,11 @@ type rubiconUserDataExt struct { } type rubiconUserExt struct { - Consent string `json:"consent,omitempty"` - DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` - Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` - TpID []rubiconExtUserTpID `json:"tpid,omitempty"` - RP rubiconUserExtRP `json:"rp"` - LiverampIdl string `json:"liveramp_idl,omitempty"` + Consent string `json:"consent,omitempty"` + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + TpID []rubiconExtUserTpID `json:"tpid,omitempty"` + RP rubiconUserExtRP `json:"rp"` + LiverampIdl string `json:"liveramp_idl,omitempty"` } type rubiconSiteExtRP struct { @@ -772,9 +771,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } userExtRP.Consent = userExt.Consent - if userExt.DigiTrust != nil { - userExtRP.DigiTrust = userExt.DigiTrust - } userExtRP.Eids = userExt.Eids // set user.ext.tpid diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 8f8d3fb1557..3674c872d73 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1039,11 +1039,7 @@ func TestOpenRTBRequest(t *testing.T) { PxRatio: rubidata.devicePxRatio, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"digitrust": { - "id": "some-digitrust-id", - "keyv": 1, - "pref": 0 - }, + Ext: json.RawMessage(`{ "eids": [{ "source": "pubcid", "id": "2402fc76-7b39-4f0e-bfc2-060ef7693648" @@ -1117,10 +1113,6 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request.user.ext object.") } - assert.Equal(t, "some-digitrust-id", userExt.DigiTrust.ID, "DigiTrust ID id not as expected!") - assert.Equal(t, 1, userExt.DigiTrust.KeyV, "DigiTrust KeyV id not as expected!") - assert.Equal(t, 0, userExt.DigiTrust.Pref, "DigiTrust Pref id not as expected!") - assert.NotNil(t, userExt.Eids) assert.Equal(t, 1, len(userExt.Eids), "Eids values are not as expected!") assert.Contains(t, userExt.Eids, openrtb_ext.ExtUserEid{Source: "pubcid", ID: "2402fc76-7b39-4f0e-bfc2-060ef7693648"}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index a90670e53be..408cbb7979d 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -146,7 +146,6 @@ } ], "ext": { - "digitrust": null, "rp": { "target": { "iab": [ diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 7322861efd5..a0e78a4a8eb 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -32,7 +32,6 @@ func TestGoodAmpRequests(t *testing.T) { goodRequests := map[string]json.RawMessage{ "1": json.RawMessage(validRequest(t, "aliased-buyeruids.json")), "2": json.RawMessage(validRequest(t, "aliases.json")), - "4": json.RawMessage(validRequest(t, "digitrust.json")), "5": json.RawMessage(validRequest(t, "gdpr-no-consentstring.json")), "6": json.RawMessage(validRequest(t, "gdpr.json")), "7": json.RawMessage(validRequest(t, "site.json")), @@ -122,11 +121,6 @@ func TestAMPPageInfo(t *testing.T) { func TestGDPRConsent(t *testing.T) { consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY" existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" - digitrust := &openrtb_ext.ExtUserDigiTrust{ - ID: "anyDigitrustID", - KeyV: 1, - Pref: 0, - } testCases := []struct { description string @@ -165,12 +159,10 @@ func TestGDPRConsent(t *testing.T) { description: "Overrides Existing Consent - With Sibling Data", consent: consent, userExt: &openrtb_ext.ExtUser{ - Consent: existingConsent, - DigiTrust: digitrust, + Consent: existingConsent, }, expectedUserExt: openrtb_ext.ExtUser{ - Consent: consent, - DigiTrust: digitrust, + Consent: consent, }, }, { diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d8a7fa689b9..c9f2bbdb68f 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1124,13 +1124,9 @@ func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]s } if user.Ext != nil { - // Creating ExtUser object to check if DigiTrust is valid + // Creating ExtUser object var userExt openrtb_ext.ExtUser if err := json.Unmarshal(user.Ext, &userExt); err == nil { - if userExt.DigiTrust != nil && userExt.DigiTrust.Pref != 0 { - // DigiTrust is not valid. Return error. - return errors.New("request.user contains a digitrust object that is not valid.") - } // Check if the buyeruids are valid if userExt.Prebid != nil { if len(userExt.Prebid.BuyerUIDs) < 1 { diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json b/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json deleted file mode 100644 index 1fb7169fced..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "description": "Invalid digitrust object in user extension", - "mockBidRequest": { - "id": "request-with-invalid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 1 - } - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user contains a digitrust object that is not valid.\n" -} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json deleted file mode 100644 index 5cd070745ab..00000000000 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "Well formed amp request with digitrust extension that should run properly", - "mockBidRequest": { - "id": "request-with-valid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 0 - } - } - } - }, - "expectedBidResponse": { - "id":"request-with-valid-digitrust-obj", - "bidid":"test bid id", - "nbr":0 - }, - "expectedReturnCode": 200 -} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index c0a1a1c9307..48b77bb2725 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1361,7 +1361,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ COPPA: 1, @@ -1523,7 +1523,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json index 9bd4c02fb42..f9fb3264c3c 100644 --- a/exchange/exchangetest/request-other-user-ext.json +++ b/exchange/exchangetest/request-other-user-ext.json @@ -12,11 +12,6 @@ "buyeruids": { "appnexus": "explicit-appnexus" } - }, - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 } } }, @@ -48,14 +43,7 @@ }, "user": { "id": "foo", - "buyeruid": "explicit-appnexus", - "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } - } + "buyeruid": "explicit-appnexus" }, "imp": [ { diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json index bb36ba8aeeb..aae11606baa 100644 --- a/exchange/exchangetest/request-user-no-prebid.json +++ b/exchange/exchangetest/request-user-no-prebid.json @@ -8,11 +8,6 @@ "user": { "id": "foo", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ @@ -45,11 +40,6 @@ "id": "foo", "buyeruid": "implicit-appnexus", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ diff --git a/exchange/utils.go b/exchange/utils.go index 7f69b341704..405d06a516a 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -378,7 +378,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { userExt.Prebid = nil // Remarshal (instead of removing) if the ext has other known fields - if userExt.Consent != "" || userExt.DigiTrust != nil || len(userExt.Eids) > 0 { + if userExt.Consent != "" || len(userExt.Eids) > 0 { if newUserExtBytes, err := json.Marshal(userExt); err != nil { return nil, err } else { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5a281f9a360..5a9fa187f62 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1788,7 +1788,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), @@ -1833,7 +1833,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index b83f82330db..d5e6ae678cc 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -10,11 +10,6 @@ type ExtUser struct { Prebid *ExtUserPrebid `json:"prebid,omitempty"` - // DigiTrust breaks the typical Prebid Server convention of namespacing "global" options inside "ext.prebid.*" - // to match the recommendation from the broader digitrust community. - // For more info, see: https://github.com/digi-trust/dt-cdn/wiki/OpenRTB-extension#openrtb-2x - DigiTrust *ExtUserDigiTrust `json:"digitrust,omitempty"` - Eids []ExtUserEid `json:"eids,omitempty"` } @@ -23,14 +18,6 @@ type ExtUserPrebid struct { BuyerUIDs map[string]string `json:"buyeruids,omitempty"` } -// ExtUserDigiTrust defines the contract for bidrequest.user.ext.digitrust -// More info on DigiTrust can be found here: https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide -type ExtUserDigiTrust struct { - ID string `json:"id"` // Unique device identifier - KeyV int `json:"keyv"` // Key version used to encrypt ID - Pref int `json:"pref"` // User optout preference -} - // ExtUserEid defines the contract for bidrequest.user.ext.eids // Responsible for the Universal User ID support: establishing pseudonymous IDs for users. // See https://github.com/prebid/Prebid.js/issues/3900 for details. diff --git a/privacy/scrubber.go b/privacy/scrubber.go index edaa5bb07c6..e07ebd0581b 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -225,11 +225,8 @@ func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage { } _, hasEids := userExtParsed["eids"] - _, hasDigitrust := userExtParsed["digitrust"] - if hasEids || hasDigitrust { + if hasEids { delete(userExtParsed, "eids") - delete(userExtParsed, "digitrust") - result, err := json.Marshal(userExtParsed) if err == nil { return result diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 9207315f593..1198d0bbc9c 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -202,7 +202,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -327,7 +327,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserNone, @@ -340,7 +340,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, @@ -359,7 +359,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -586,18 +586,18 @@ func TestScrubUserExtIDs(t *testing.T) { expected: json.RawMessage(`{"anyExisting":42}}`), }, { - description: "Remove eids + digitrust", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids", + userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{}`), }, { - description: "Remove eids + digitrust - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - With Other Data", + userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":42}`), }, { - description: "Remove eids + digitrust - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - With Other Nested Data", + userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, { @@ -620,21 +620,6 @@ func TestScrubUserExtIDs(t *testing.T) { userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, - { - description: "Remove digitrust Only", - userExt: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{}`), - }, - { - description: "Remove digitrust Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove digitrust Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { From 0667f934c4e64e620d7312ef0104b10e718413d6 Mon Sep 17 00:00:00 2001 From: Mani Gandham Date: Thu, 1 Jul 2021 10:21:22 -0700 Subject: [PATCH 594/603] IX: merge eventtrackers with imptrackers for native bid responses (#1900) --- adapters/ix/ix.go | 57 +++++++++- .../native-eventtrackers-compat-12.json | 104 ++++++++++++++++++ .../ix/ixtest/supplemental/bad-imp-id.json | 2 +- .../native-eventtrackers-empty.json | 104 ++++++++++++++++++ .../native-eventtrackers-missing.json | 104 ++++++++++++++++++ .../ixtest/supplemental/native-missing.json | 104 ++++++++++++++++++ 6 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json create mode 100644 adapters/ix/ixtest/supplemental/native-missing.json diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 5e10138f8f3..b251ec0f736 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -7,7 +7,10 @@ import ( "fmt" "io/ioutil" "net/http" + "sort" + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -400,7 +403,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque for _, bid := range seatBid.Bid { bidType, ok := impMediaType[bid.ImpID] if !ok { - errs = append(errs, fmt.Errorf("Unmatched impression id: %s.", bid.ImpID)) + errs = append(errs, fmt.Errorf("unmatched impression id: %s", bid.ImpID)) } var bidExtVideo *openrtb_ext.ExtBidPrebidVideo @@ -417,6 +420,28 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque } } + var bidNative1v1 *Native11Wrapper + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v1) + if err == nil && len(bidNative1v1.Native.EventTrackers) > 0 { + mergeNativeImpTrackers(&bidNative1v1.Native) + if json, err := json.Marshal(bidNative1v1); err == nil { + bid.AdM = string(json) + } + } + } + + var bidNative1v2 *native1response.Response + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v2) + if err == nil && len(bidNative1v2.EventTrackers) > 0 { + mergeNativeImpTrackers(bidNative1v2) + if json, err := json.Marshal(bidNative1v2); err == nil { + bid.AdM = string(json) + } + } + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, @@ -444,3 +469,33 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } return bidder, nil } + +// native 1.2 to 1.1 tracker compatibility handling + +type Native11Wrapper struct { + Native native1response.Response `json:"native,omitempty"` +} + +func mergeNativeImpTrackers(bidNative *native1response.Response) { + + // create unique list of imp pixels urls from `imptrackers` and `eventtrackers` + uniqueImpPixels := map[string]struct{}{} + for _, v := range bidNative.ImpTrackers { + uniqueImpPixels[v] = struct{}{} + } + + for _, v := range bidNative.EventTrackers { + if v.Event == native1.EventTypeImpression && v.Method == native1.EventTrackingMethodImage { + uniqueImpPixels[v.URL] = struct{}{} + } + } + + // rewrite `imptrackers` with new deduped list of imp pixels + bidNative.ImpTrackers = make([]string, 0) + for k := range uniqueImpPixels { + bidNative.ImpTrackers = append(bidNative.ImpTrackers, k) + } + + // sort so tests pass correctly + sort.Strings(bidNative.ImpTrackers) +} diff --git a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json new file mode 100644 index 00000000000..36a239987a6 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"eventtrackers\":[{\"url\":\"https://example.com/imp-2.gif\",\"event\":1,\"method\":1},{\"url\":\"https://example.com/imp-3.gif\",\"event\":1,\"method\":1}],\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\",\"https://example.com/imp-3.gif\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-2.gif\"},{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-3.gif\"}]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/bad-imp-id.json b/adapters/ix/ixtest/supplemental/bad-imp-id.json index 0b852c85d2b..1ca053b674e 100644 --- a/adapters/ix/ixtest/supplemental/bad-imp-id.json +++ b/adapters/ix/ixtest/supplemental/bad-imp-id.json @@ -111,7 +111,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "Unmatched impression id: bad-imp-id.", + "value": "unmatched impression id: bad-imp-id", "comparison": "literal" } ] diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json new file mode 100644 index 00000000000..4cf314e742f --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json new file mode 100644 index 00000000000..d8c78a5cbca --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-missing.json b/adapters/ix/ixtest/supplemental/native-missing.json new file mode 100644 index 00000000000..ec2108ce5d1 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} From aa443b3e51e8da94b7643cce17c99083ba213c2f Mon Sep 17 00:00:00 2001 From: armon823 <86739148+armon823@users.noreply.github.com> Date: Wed, 7 Jul 2021 09:13:06 -0700 Subject: [PATCH 595/603] Inmobi: user sync (#1911) --- adapters/inmobi/usersync.go | 12 ++++++++++++ static/bidder-info/inmobi.yaml | 2 +- usersync/usersyncers/syncer.go | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 adapters/inmobi/usersync.go diff --git a/adapters/inmobi/usersync.go b/adapters/inmobi/usersync.go new file mode 100644 index 00000000000..7f022e3c5d0 --- /dev/null +++ b/adapters/inmobi/usersync.go @@ -0,0 +1,12 @@ +package inmobi + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewInmobiSyncer(template *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("inmobi", template, adapters.SyncTypeRedirect) +} diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 9b11640f262..634c03481de 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,5 +1,5 @@ maintainer: - email: "prebid-support@inmobi.com" + email: "technology-irv@inmobi.com" gvlVendorID: 333 capabilities: app: diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7472bf3a70d..53472018e30 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -49,6 +49,7 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" @@ -150,6 +151,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderGrid, grid.NewGridSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderGumGum, gumgum.NewGumGumSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderInMobi, inmobi.NewInmobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) From 2d0a9ec81c009c7ba2f2c7beeb24e7f0b6b4e013 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 8 Jul 2021 18:23:54 +0300 Subject: [PATCH 596/603] Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 81 ++++++---- adapters/rubicon/rubicon_test.go | 28 ++++ .../rubicontest/exemplary/simple-video.json | 153 +++++++++++++++--- 3 files changed, 210 insertions(+), 52 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 84d8596449c..579af9839c7 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -91,8 +91,8 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } -type rubiconUserDataExt struct { - TaxonomyName string `json:"taxonomyname"` +type rubiconDataExt struct { + SegTax int `json:"segtax"` } type rubiconUserExt struct { @@ -104,7 +104,8 @@ type rubiconUserExt struct { } type rubiconSiteExtRP struct { - SiteID int `json:"site_id"` + SiteID int `json:"site_id"` + Target json.RawMessage `json:"target,omitempty"` } type rubiconSiteExt struct { @@ -755,12 +756,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if request.User != nil { userCopy := *request.User - userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} - if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil { + target, err := updateExtWithIabAttribute(rubiconExt.Visitor, userCopy.Data, []int{4}) + if err != nil { errs = append(errs, err) continue } + userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} if request.User.Ext != nil { var userExt *openrtb_ext.ExtUser @@ -845,19 +847,30 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada thisImp.Video = nil } - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}} if request.Site != nil { siteCopy := *request.Site - siteCopy.Ext, err = json.Marshal(&siteExt) + siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} + target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = target + + siteCopy.Ext, err = json.Marshal(&siteExtRP) + if err != nil { + errs = append(errs, err) + continue + } + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.Site = &siteCopy - } - if request.App != nil { + } else { appCopy := *request.App - appCopy.Ext, err = json.Marshal(&siteExt) + appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}) appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy @@ -904,41 +917,53 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s return bidFloor, bidFloorCur } -func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { +func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { + var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + + extRPTarget := make(map[string]interface{}) + + if target != nil { + if err := json.Unmarshal(target, &extRPTarget); err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + } + + extRPTarget["iab"] = segmentIdsToCopy + + jsonTarget, err := json.Marshal(&extRPTarget) + if err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + return jsonTarget, nil +} + +func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { var segmentIdsToCopy = make([]string, 0) for _, dataRecord := range data { if dataRecord.Ext != nil { - var dataExtObject rubiconUserDataExt + var dataExtObject rubiconDataExt err := json.Unmarshal(dataRecord.Ext, &dataExtObject) if err != nil { continue } - if strings.EqualFold(dataExtObject.TaxonomyName, "iab") { + if contains(segTaxValues, dataExtObject.SegTax) { for _, segment := range dataRecord.Segment { segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) } } } } + return segmentIdsToCopy +} - userExtRPTarget := make(map[string]interface{}) - - if userExtRP.RP.Target != nil { - if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} +func contains(s []int, e int) bool { + for _, a := range s { + if a == e { + return true } } - - userExtRPTarget["iab"] = segmentIdsToCopy - - if target, err := json.Marshal(&userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } else { - userExtRP.RP.Target = target - } - - return nil + return false } func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 3674c872d73..76904a42137 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1035,6 +1035,10 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, @@ -1146,6 +1150,10 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { "visitor": {"key2" : "val2"} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1195,6 +1203,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1250,6 +1262,10 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1283,6 +1299,10 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { @@ -1391,6 +1411,10 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1436,6 +1460,10 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 408cbb7979d..11afdd50d2b 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -9,11 +9,50 @@ "id": "1", "bundle": "com.wls.testwlsapplication" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -23,7 +62,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -33,7 +72,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -43,13 +92,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -100,11 +149,69 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + }, + "ext": { + "rp": { + "site_id": 113932, + "target": { + "iab": [ + "segmentId1", + "segmentId2", + "segmentId3" + ] + } + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -114,7 +221,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -124,7 +231,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -134,13 +251,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -158,18 +275,6 @@ }, "app": { "id": "1", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - }, "bundle": "com.wls.testwlsapplication" }, "imp": [ From cc51af9bf18b316c7dfd03e216ef84abab4db453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Fern=C3=A1ndez?= Date: Thu, 8 Jul 2021 19:29:46 +0200 Subject: [PATCH 597/603] New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments --- adapters/axonix/axonix.go | 116 +++++++++++++ adapters/axonix/axonix_test.go | 30 ++++ .../exemplary/banner-and-video.json | 133 +++++++++++++++ .../exemplary/banner-video-native.json | 157 ++++++++++++++++++ .../axonixtest/exemplary/simple-banner.json | 105 ++++++++++++ .../axonixtest/exemplary/simple-video.json | 86 ++++++++++ .../axonix/axonixtest/params/race/banner.json | 3 + .../axonix/axonixtest/params/race/video.json | 3 + .../supplemental/bad-response-no-body.json | 57 +++++++ .../supplemental/status-bad-request.json | 58 +++++++ .../supplemental/status-no-content.json | 53 ++++++ .../supplemental/unexpected-status-code.json | 58 +++++++ .../supplemental/valid-extension.json | 86 ++++++++++ .../supplemental/valid-with-device.json | 93 +++++++++++ adapters/axonix/params_test.go | 59 +++++++ config/config.go | 1 + openrtb_ext/imp_axonix.go | 5 + static/bidder-info/axonix.yaml | 15 ++ static/bidder-params/axonix.json | 14 ++ 19 files changed, 1132 insertions(+) create mode 100644 adapters/axonix/axonix.go create mode 100644 adapters/axonix/axonix_test.go create mode 100644 adapters/axonix/axonixtest/exemplary/banner-and-video.json create mode 100644 adapters/axonix/axonixtest/exemplary/banner-video-native.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-banner.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-video.json create mode 100644 adapters/axonix/axonixtest/params/race/banner.json create mode 100644 adapters/axonix/axonixtest/params/race/video.json create mode 100644 adapters/axonix/axonixtest/supplemental/bad-response-no-body.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-bad-request.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-no-content.json create mode 100644 adapters/axonix/axonixtest/supplemental/unexpected-status-code.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-extension.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-with-device.json create mode 100644 adapters/axonix/params_test.go create mode 100644 openrtb_ext/imp_axonix.go create mode 100644 static/bidder-info/axonix.yaml create mode 100644 static/bidder-params/axonix.json diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go new file mode 100644 index 00000000000..d3016235319 --- /dev/null +++ b/adapters/axonix/axonix.go @@ -0,0 +1,116 @@ +package axonix + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + URI string +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + var axonixExt openrtb_ext.ExtImpAxonix + if err := json.Unmarshal(bidderExt.Bidder, &axonixExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + thisURI := a.URI + if len(thisURI) == 0 { + thisURI = "https://openrtb-us-east-1.axonix.com/supply/prebid-server/" + axonixExt.SupplyId + } + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: thisURI, + Body: requestJSON, + Headers: headers, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + b := &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaType(bid.ImpID, request.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func getMediaType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impId { + if imp.Native != nil { + return openrtb_ext.BidTypeNative + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go new file mode 100644 index 00000000000..6c4a3eb34d6 --- /dev/null +++ b/adapters/axonix/axonix_test.go @@ -0,0 +1,30 @@ +package axonix + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamplesWithConfiguredURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{ + Endpoint: "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} + +func TestJsonSamplesWithHardcodedURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-and-video.json b/adapters/axonix/axonixtest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..1755cd0ef22 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-and-video.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-video-native.json b/adapters/axonix/axonixtest/exemplary/banner-video-native.json new file mode 100644 index 00000000000..3944eb358b9 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-video-native.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-banner.json b/adapters/axonix/axonixtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..581e59b9b9e --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-video.json b/adapters/axonix/axonixtest/exemplary/simple-video.json new file mode 100644 index 00000000000..c15d7876470 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-video.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/params/race/banner.json b/adapters/axonix/axonixtest/params/race/banner.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/params/race/video.json b/adapters/axonix/axonixtest/params/race/video.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json new file mode 100644 index 00000000000..f89f40f122d --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-bad-request.json b/adapters/axonix/axonixtest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..d64a855e348 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-bad-request.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-no-content.json b/adapters/axonix/axonixtest/supplemental/status-no-content.json new file mode 100644 index 00000000000..96dd899c1fb --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-no-content.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..e7a7be7847e --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/valid-extension.json b/adapters/axonix/axonixtest/supplemental/valid-extension.json new file mode 100644 index 00000000000..372f24d4f76 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-extension.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/valid-with-device.json b/adapters/axonix/axonixtest/supplemental/valid-with-device.json new file mode 100644 index 00000000000..62f4ea06b5a --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-with-device.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go new file mode 100644 index 00000000000..e9c0cc5b83e --- /dev/null +++ b/adapters/axonix/params_test.go @@ -0,0 +1,59 @@ +package axonix + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/axonix.json +// +// These also validate the format of the external API: request.imp[i].ext.axonix + +// TestValidParams makes sure that the Axonix schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderAxonix, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Axonix params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Axonix schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAxonix, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8"}`, + `{"supplyId": "test"}`, +} + +var invalidParams = []string{ + `{"supplyId": 100}`, + `{"supplyId": false}`, + `{"supplyId": true}`, + `{"supplyId": 123}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/config/config.go b/config/config.go index 35545c38c6f..fedfa7503e9 100644 --- a/config/config.go +++ b/config/config.go @@ -854,6 +854,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.audiencenetwork.disabled", true) v.SetDefault("adapters.audiencenetwork.endpoint", "https://an.facebook.com/placementbid.ortb") v.SetDefault("adapters.avocet.disabled", true) + v.SetDefault("adapters.axonix.disabled", true) v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") diff --git a/openrtb_ext/imp_axonix.go b/openrtb_ext/imp_axonix.go new file mode 100644 index 00000000000..7dd9f68418d --- /dev/null +++ b/openrtb_ext/imp_axonix.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAxonix struct { + SupplyId string `json:"supplyId"` +} diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml new file mode 100644 index 00000000000..3c73501d9cc --- /dev/null +++ b/static/bidder-info/axonix.yaml @@ -0,0 +1,15 @@ +maintainer: + email: support.axonix@emodoinc.com +gvlVendorID: 678 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/axonix.json b/static/bidder-params/axonix.json new file mode 100644 index 00000000000..7a3762ce5e2 --- /dev/null +++ b/static/bidder-params/axonix.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Axonix Adapter Params", + "description": "A schema which validates params accepted by the Axonix adapter", + "type": "object", + "properties": { + "supplyId": { + "type": "string", + "minLength": 1, + "description": "Unique supply identifier" + } + }, + "required": ["supplyId"] +} From c35846ade9cab0c568e5fb115f8089a934eef33b Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 9 Jul 2021 11:44:09 -0400 Subject: [PATCH 598/603] Rubicon: Fix Nil Reference Panic (#1918) --- adapters/rubicon/rubicon.go | 17 +- .../supplemental/no-site-content-data.json | 293 ++++++++++++++++++ .../supplemental/no-site-content.json | 289 +++++++++++++++++ 3 files changed, 593 insertions(+), 6 deletions(-) create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content-data.json create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 579af9839c7..84200431992 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -852,12 +852,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if request.Site != nil { siteCopy := *request.Site siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} - target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) - if err != nil { - errs = append(errs, err) - continue + if siteCopy.Content != nil { + target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = target } - siteExtRP.RP.Target = target siteCopy.Ext, err = json.Marshal(&siteExtRP) if err != nil { @@ -919,6 +921,9 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + if len(segmentIdsToCopy) == 0 { + return target, nil + } extRPTarget := make(map[string]interface{}) @@ -938,7 +943,7 @@ func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, seg } func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { - var segmentIdsToCopy = make([]string, 0) + var segmentIdsToCopy = make([]string, 0, len(data)) for _, dataRecord := range data { if dataRecord.Ext != nil { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json new file mode 100644 index 00000000000..f67788a3154 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -0,0 +1,293 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + "content": { + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "ext": { + "bidder": { + "video": { + }, + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "content": { + }, + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ], + "ext": { + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "ext": { + "rp": { + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json new file mode 100644 index 00000000000..d3b8f8b7454 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -0,0 +1,289 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "ext": { + "bidder": { + "video": { + }, + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "uri?tk_xint=pbs-test-tracker", + "body": { + "id": "test-request-id", + "device": { + "ext": { + "rp": { + "pixelratio": 0 + } + }, + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ], + "ext": { + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "ext": { + "rp": { + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} From 1adc758e4acc4f8672771f6e602987a1428a2887 Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Mon, 2 Aug 2021 23:54:05 +0530 Subject: [PATCH 599/603] git rebase --- adapters/appnexus/appnexus.go | 29 +- .../video-same-adpodid-two-imps-same-pod.json | 1 - adapters/audienceNetwork/facebook.go | 34 - .../supplemental/test_params.json | 8 +- adapters/pubmatic/pubmatic.go | 37 +- adapters/pubmatic/pubmatic_test.go | 6 +- adapters/yieldmo/yieldmo.go | 10 - analytics/config/config_test.go | 2 - analytics/filesystem/file_module_test.go | 2 - config/config.go | 71 +- endpoints/openrtb2/amp_auction_test.go | 18 - endpoints/openrtb2/auction_benchmark_test.go | 1 - endpoints/openrtb2/auction_test.go | 47 -- endpoints/openrtb2/video_auction_test.go | 60 -- errortypes/code.go | 2 + exchange/adapter_builders.go | 241 +++--- exchange/exchange.go | 110 ++- exchange/exchange_test.go | 715 ++++++++++++++++-- exchange/legacy.go | 375 --------- exchange/targeting_test.go | 209 ----- gdpr/impl.go | 41 +- go.mod | 2 +- go.sum | 4 + metrics/config/metrics.go | 55 +- metrics/go_metrics.go | 45 +- metrics/metrics.go | 1 + metrics/metrics_mock.go | 7 +- metrics/prometheus/prometheus.go | 20 +- metrics/prometheus/prometheus_test.go | 24 +- metrics/prometheus/type_conversion.go | 36 - openrtb_ext/bidders.go | 244 +++--- openrtb_ext/bidders_test.go | 61 -- openrtb_ext/request.go | 7 + privacy/gdpr/policy_test.go | 31 +- privacy/policies.go | 25 - router/router.go | 1 - usersync/usersyncers/syncer_test.go | 54 +- 37 files changed, 1250 insertions(+), 1386 deletions(-) delete mode 100644 exchange/legacy.go diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 5daf3c25216..cb0286d1851 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "math/rand" @@ -301,11 +302,20 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad defaultDisplayManagerVer = fmt.Sprintf("%s-%s", source, version) } } + var adPodId *bool + for i := 0; i < len(request.Imp); i++ { - memberId, err := preprocess(&request.Imp[i], defaultDisplayManagerVer) + memberId, impAdPodId, err := preprocess(&request.Imp[i], defaultDisplayManagerVer) if memberId != "" { memberIds[memberId] = true } + if adPodId == nil { + adPodId = &impAdPodId + } else if *adPodId != impAdPodId { + errs = append(errs, errors.New("generate ad pod option should be same for all pods in request")) + return nil, errs + } + // If the preprocessing failed, the server won't be able to bid on this Imp. Delete it, and note the error. if err != nil { errs = append(errs, err) @@ -364,12 +374,13 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad // Commenting out the following piece of code to avoid populating adpod_id in the Appnexus request (ref: https://inside.pubmatic.com:9443/jira/browse/UOE-6196) - // For long form requests adpod_id must be sent downstream. + // For long form requests if adpodId feature enabled, adpod_id must be sent downstream. // Adpod id is a unique identifier for pod // All impressions in the same pod must have the same pod id in request extension // For this all impressions in request should belong to the same pod // If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same - /*if isVIDEO == 1 { + // If adpodId feature disabled and impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but do not include ad pod id + /*if isVIDEO == 1 && *adPodId { podImps := groupByPods(imps) requests := make([]*adapters.RequestData, 0, len(podImps)) @@ -465,15 +476,15 @@ func keys(m map[string]bool) []string { // preprocess mutates the imp to get it ready to send to appnexus. // // It returns the member param, if it exists, and an error if anything went wrong during the preprocessing. -func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, error) { +func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, bool, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return "", err + return "", false, err } var appnexusExt openrtb_ext.ExtImpAppnexus if err := json.Unmarshal(bidderExt.Bidder, &appnexusExt); err != nil { - return "", err + return "", false, err } // Accept legacy Appnexus parameters if we don't have modern ones @@ -489,7 +500,7 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, err } if appnexusExt.PlacementId == 0 && (appnexusExt.InvCode == "" || appnexusExt.Member == "") { - return "", &errortypes.BadInput{ + return "", false, &errortypes.BadInput{ Message: "No placement or member+invcode provided", } } @@ -531,10 +542,10 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, err }} var err error if imp.Ext, err = json.Marshal(&impExt); err != nil { - return appnexusExt.Member, err + return appnexusExt.Member, appnexusExt.AdPodId, err } - return appnexusExt.Member, nil + return appnexusExt.Member, appnexusExt.AdPodId, nil } func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { diff --git a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json index 5a453979f7c..d0940a2345d 100644 --- a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json +++ b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json @@ -47,7 +47,6 @@ "id": "test-request-id", "ext": { "appnexus": { - "adpod_id": "5577006791947779410", "hb_source": 6 }, "prebid": {} diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 4ae122f9065..63138c42d0f 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -463,37 +463,3 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (* return &timeoutReq, nil } - -func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) { - var ( - rID string - pubID string - err error - ) - - // Note, the facebook adserver can only handle single impression requests, so we have to split multi-imp requests into - // multiple request. In order to ensure that every split request has a unique ID, the split request IDs are set to the - // corresponding imp's ID - rID, err = jsonparser.GetString(req.Body, "id") - if err != nil { - return &adapters.RequestData{}, []error{err} - } - - // The publisher ID is expected in the app object - pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id") - if err != nil { - return &adapters.RequestData{}, []error{ - errors.New("path app.publisher.id not found in the request"), - } - } - - uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, pubID, rID) - timeoutReq := adapters.RequestData{ - Method: "GET", - Uri: uri, - Body: nil, - Headers: http.Header{}, - } - - return &timeoutReq, nil -} diff --git a/adapters/conversant/conversanttest/supplemental/test_params.json b/adapters/conversant/conversanttest/supplemental/test_params.json index 403bcc42226..cf71299df0f 100644 --- a/adapters/conversant/conversanttest/supplemental/test_params.json +++ b/adapters/conversant/conversanttest/supplemental/test_params.json @@ -107,7 +107,7 @@ "bidfloor": 7, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -126,7 +126,7 @@ "bidfloor": 1, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -154,7 +154,7 @@ "bidfloor": 7, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], @@ -182,7 +182,7 @@ "bidfloor": -3, "secure": 1, "tagid": "mytag", - "displaymanager": "prebid-s2s", + "displaymanager": "pubmatic-openwrap", "displaymanagerver": "2.0.0", "video": { "api": [1,2], diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index d787846c76b..f88a35bef8d 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -196,8 +196,8 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) + pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) if len(params.Keywords) != 0 { kvstr := prepareImpressionExt(params.Keywords) @@ -564,9 +564,8 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error { } //In case of video, size could be derived from the player size - if imp.Banner != nil && height != 0 && width != 0 { - imp.Banner.H = openrtb2.Int64Ptr(int64(height)) - imp.Banner.W = openrtb2.Int64Ptr(int64(width)) + if imp.Banner != nil { + imp.Banner = assignBannerWidthAndHeight(imp.Banner, int64(width), int64(height)) } } else { return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) @@ -575,25 +574,23 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error { return nil } -func assignBannerSize(banner *openrtb2.Banner) error { - if banner == nil { - return nil - } - +func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W != nil && banner.H != nil { - return nil + return banner, nil } if len(banner.Format) == 0 { - return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) + return nil, errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(int64) - *banner.W = banner.Format[0].W - banner.H = new(int64) - *banner.H = banner.Format[0].H + return assignBannerWidthAndHeight(banner, banner.Format[0].W, banner.Format[0].H), nil +} - return nil +func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.Banner { + bannerCopy := *banner + bannerCopy.W = openrtb2.Int64Ptr(w) + bannerCopy.H = openrtb2.Int64Ptr(h) + return &bannerCopy } // parseImpressionObject parse the imp to get it ready to send to pubmatic @@ -635,14 +632,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID } if imp.Banner != nil { - if err := assignBannerSize(imp.Banner); err != nil { + bannerCopy, err := assignBannerSize(imp.Banner) + if err != nil { return err } + imp.Banner = bannerCopy } - imp.Ext = nil - - impExtMap := make(map[string]interface{}) + impExtMap := make(map[string]interface{}, 0) if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 { addKeywordsToExt(pubmaticExt.Keywords, impExtMap) } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index b0df1903a42..1a15f221130 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -73,7 +73,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { var bids []openrtb2.Bid for i, imp := range breq.Imp { - bid := openrtb2.Bid{ + bids = append(bids, openrtb2.Bid{ ID: fmt.Sprintf("SeatID_%d", i), ImpID: imp.ID, Price: float64(int(rand.Float64()*1000)) / 100, @@ -83,9 +83,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { W: *imp.Banner.W, H: *imp.Banner.H, DealID: fmt.Sprintf("DealID_%d", i), - } - - bids = append(bids, bid) + }) } resp.SeatBid[0].Bid = bids diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 61e4c455502..7d7a8f22b01 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -149,13 +149,3 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { } return openrtb_ext.BidTypeVideo } - -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { - //default to video unless banner exists in impression - for _, imp := range imps { - if imp.ID == impId && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } - } - return openrtb_ext.BidTypeVideo -} diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 6589131bd61..fe95a66ed6f 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -7,8 +7,6 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" ) diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 05741fe8642..45a72266569 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -8,8 +8,6 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/usersync" ) diff --git a/config/config.go b/config/config.go index fedfa7503e9..6d5b65bcdd4 100644 --- a/config/config.go +++ b/config/config.go @@ -98,7 +98,7 @@ type HTTPClient struct { DialKeepAlive int `mapstructure:"dial_keepalive"` } -func (cfg *Configuration) validate() []error { +func (cfg *Configuration) validate(v *viper.Viper) []error { var errs []error errs = cfg.AuctionTimeouts.validate(errs) errs = cfg.StoredRequests.validate(errs) @@ -110,7 +110,7 @@ func (cfg *Configuration) validate() []error { if cfg.MaxRequestSize < 0 { errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize)) } - errs = cfg.GDPR.validate(errs) + errs = cfg.GDPR.validate(v, errs) errs = cfg.CurrencyConverter.validate(errs) errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) @@ -198,22 +198,26 @@ type Privacy struct { type GDPR struct { Enabled bool `mapstructure:"enabled"` HostVendorID int `mapstructure:"host_vendor_id"` - UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` + DefaultValue string `mapstructure:"default_value"` Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]struct{} - TCF1 TCF1 `mapstructure:"tcf1"` TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` // Deprecated: Use account-level GDPR settings (gdpr.integration_enabled.amp) instead // EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies. // If the gdpr flag is unset in a request, but geo.country is set, we will assume GDPR applies if and only // if the country matches one on this list. If both the GDPR flag and country are not set, we default - // to UsersyncIfAmbiguous + // to DefaultValue EEACountries []string `mapstructure:"eea_countries"` EEACountriesMap map[string]struct{} } -func (cfg *GDPR) validate(errs []error) []error { +func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error { + if !v.IsSet("gdpr.default_value") { + errs = append(errs, fmt.Errorf("gdpr.default_value is required and must be specified")) + } else if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { + errs = append(errs, fmt.Errorf("gdpr.default_value must be 0 or 1")) + } if cfg.HostVendorID < 0 || cfg.HostVendorID > 0xffff { errs = append(errs, fmt.Errorf("gdpr.host_vendor_id must be in the range [0, %d]. Got %d", 0xffff, cfg.HostVendorID)) } @@ -223,9 +227,6 @@ func (cfg *GDPR) validate(errs []error) []error { if cfg.AMPException == true { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } - if cfg.TCF1.FetchGVL == true { - errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")) - } return errs } @@ -242,20 +243,14 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } -// TCF1 defines the TCF1 specific configurations for GDPR -type TCF1 struct { - FetchGVL bool `mapstructure:"fetch_gvl"` // Deprecated: In a future version TCF1 will always use the fallback GVL - FallbackGVLPath string `mapstructure:"fallback_gvl_path"` -} - // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { - Enabled bool `mapstructure:"enabled"` - Purpose1 PurposeDetail `mapstructure:"purpose1"` - Purpose2 PurposeDetail `mapstructure:"purpose2"` - Purpose7 PurposeDetail `mapstructure:"purpose7"` - SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` - PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"` + Enabled bool `mapstructure:"enabled"` + Purpose1 PurposeDetail `mapstructure:"purpose1"` + Purpose2 PurposeDetail `mapstructure:"purpose2"` + Purpose7 PurposeDetail `mapstructure:"purpose7"` + SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` + PurposeOneTreatment PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } // Making a purpose struct so purpose specific details can be added later. @@ -263,7 +258,7 @@ type PurposeDetail struct { Enabled bool `mapstructure:"enabled"` } -type PurposeOneTreatement struct { +type PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -532,7 +527,7 @@ func New(v *viper.Viper) (*Configuration, error) { glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") - if errs := c.validate(); len(errs) > 0 { + if errs := c.validate(v); len(errs) > 0 { return &c, errortypes.NewAggregateError("validation errors", errs) } @@ -621,6 +616,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderInMobi, "https://id5-sync.com/i/495/0.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dinmobi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BID5UID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=186523&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") // openrtb_ext.BidderInvibes doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") @@ -660,7 +656,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") // openrtb_ext.BidderUnicorn doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://sync.1rx.io/usersync2/rmpssp?sub=openwrap&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderVASTBidder doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") @@ -725,6 +721,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client_cache.max_connections_per_host", 0) // unlimited + v.SetDefault("http_client_cache.max_idle_connections", 10) + v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) + v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) v.SetDefault("http_client.tls_handshake_timeout", 0) //no timeout v.SetDefault("http_client.response_header_timeout", 0) //unlimited v.SetDefault("http_client.dial_timeout", 0) //no timeout @@ -737,6 +737,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false) + v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) v.SetDefault("metrics.influxdb.host", "") v.SetDefault("metrics.influxdb.database", "") v.SetDefault("metrics.influxdb.username", "") @@ -886,6 +887,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") + v.SetDefault("adapters.interactiveoffers.endpoint", "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04") v.SetDefault("adapters.ix.disabled", false) v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919") v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") @@ -964,22 +966,19 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("analytics.pubstack.buffers.count", 100) v.SetDefault("analytics.pubstack.buffers.timeout", "900s") v.SetDefault("amp_timeout_adjustment_ms", 0) + v.BindEnv("gdpr.default_value") v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) v.SetDefault("gdpr.usersync_if_ambiguous", true) v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) - v.SetDefault("gdpr.tcf1.fetch_gvl", false) - v.SetDefault("gdpr.tcf1.fallback_gvl_path", "/home/http/GO_SERVER/dmhbserver/static/tcf1/fallback_gvl.json") v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) v.SetDefault("gdpr.tcf2.purpose2.enabled", true) v.SetDefault("gdpr.tcf2.purpose4.enabled", true) v.SetDefault("gdpr.tcf2.purpose7.enabled", true) v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", @@ -1034,6 +1033,10 @@ func SetupViper(v *viper.Viper, filename string) { // Migrate config settings to maintain compatibility with old configs migrateConfig(v) + migrateConfigPurposeOneTreatment(v) + + v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) + v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) } func migrateConfig(v *viper.Viper) { @@ -1049,6 +1052,18 @@ func migrateConfig(v *viper.Viper) { } } +func migrateConfigPurposeOneTreatment(v *viper.Viper) { + if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok { + if v.IsSet("gdpr.tcf2.purpose_one_treatment") { + glog.Warning("using gdpr.tcf2.purpose_one_treatment and ignoring deprecated gdpr.tcf2.purpose_one_treatement") + } else { + glog.Warning("gdpr.tcf2.purpose_one_treatement.enabled should be changed to gdpr.tcf2.purpose_one_treatment.enabled") + glog.Warning("gdpr.tcf2.purpose_one_treatement.access_allowed should be changed to gdpr.tcf2.purpose_one_treatment.access_allowed") + v.Set("gdpr.tcf2.purpose_one_treatment", oldConfig) + } + } +} + func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." v.SetDefault(adapterCfgPrefix+bidder+".endpoint", "") diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index a0e78a4a8eb..61164bd7272 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -849,24 +849,6 @@ func TestSizeWithMultisize(t *testing.T) { }.execute(t) } -func TestSizeWithMultisize(t *testing.T) { - formatOverrideSpec{ - width: 20, - height: 40, - multisize: "200x50,100x60", - expect: []openrtb.Format{{ - W: 20, - H: 40, - }, { - W: 200, - H: 50, - }, { - W: 100, - H: 60, - }}, - }.execute(t) -} - func TestHeightOnly(t *testing.T) { formatOverrideSpec{ height: 200, diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index f22880a1be5..e55ffd11093 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -87,7 +87,6 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { paramValidator, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, newTestMetrics(), analyticsConf.NewPBSAnalytics(&config.Analytics{}), diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 5aeef4e34ef..bcdac13dc06 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1836,53 +1836,6 @@ func TestGetAccountID(t *testing.T) { } } -func TestSChainInvalid(t *testing.T) { - deps := &endpointDeps{ - &nobidExchange{}, - newParamsValidator(t), - &mockStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{}, - pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - false, - []byte{}, - openrtb_ext.BidderMap, - nil, - nil, - hardcodedResponseIPValidator{response: true}, - } - - ui := uint64(1) - req := openrtb.BidRequest{ - ID: "someID", - Imp: []openrtb.Imp{ - { - ID: "imp-ID", - Banner: &openrtb.Banner{ - W: &ui, - H: &ui, - }, - Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), - }, - }, - Site: &openrtb.Site{ - ID: "myID", - }, - Regs: &openrtb.Regs{ - Ext: json.RawMessage(`{"us_privacy":"abcd"}`), - }, - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), - } - - errL := deps.validateRequest(&req) - - expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") - assert.ElementsMatch(t, errL, []error{expectedError}) -} - func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 2246311f317..9f0859a32cd 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1288,30 +1288,6 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) return deps } -func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) *endpointDeps { - theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}) - deps := &endpointDeps{ - ex, - newParamsValidator(t), - &mockVideoStoredReqFetcher{}, - &mockVideoStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - theMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - false, - []byte{}, - openrtb_ext.BidderMap, - ex.cache, - regexp.MustCompile(`[<>]`), - hardcodedResponseIPValidator{response: true}, - } - - return deps -} - func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { edep := &endpointDeps{ ex, @@ -1428,42 +1404,6 @@ func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r excha }, nil } -type mockExchangeAppendBidderNames struct { - lastRequest *openrtb.BidRequest - cache *mockCacheClient -} - -func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { - m.lastRequest = bidRequest - if debugLog != nil && debugLog.Enabled { - m.cache.called = true - } - ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s_appnexus","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Seat: "appnexus", - Bid: []openrtb.Bid{ - {ID: "01", ImpID: "1_0", Ext: ext}, - {ID: "02", ImpID: "1_1", Ext: ext}, - {ID: "03", ImpID: "1_2", Ext: ext}, - {ID: "04", ImpID: "1_3", Ext: ext}, - {ID: "05", ImpID: "2_0", Ext: ext}, - {ID: "06", ImpID: "2_1", Ext: ext}, - {ID: "07", ImpID: "2_2", Ext: ext}, - {ID: "08", ImpID: "3_0", Ext: ext}, - {ID: "09", ImpID: "3_1", Ext: ext}, - {ID: "10", ImpID: "3_2", Ext: ext}, - {ID: "11", ImpID: "3_3", Ext: ext}, - {ID: "12", ImpID: "3_5", Ext: ext}, - {ID: "13", ImpID: "4_0", Ext: ext}, - {ID: "14", ImpID: "5_0", Ext: ext}, - {ID: "15", ImpID: "5_1", Ext: ext}, - {ID: "16", ImpID: "5_2", Ext: ext}, - }, - }}, - }, nil -} - type mockExchangeVideoNoBids struct { lastRequest *openrtb2.BidRequest cache *mockCacheClient diff --git a/errortypes/code.go b/errortypes/code.go index f8206525b27..554357ea88a 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,6 +11,7 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode + NoConversionRateErrorCode NoBidPriceErrorCode ) @@ -20,6 +21,7 @@ const ( InvalidPrivacyConsentWarningCode = iota + 10000 AccountLevelDebugDisabledWarningCode BidderLevelDebugDisabledWarningCode + DisabledCurrencyConversionWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f5b22b2f7c1..0de64c6f26c 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -23,15 +23,20 @@ import ( "github.com/prebid/prebid-server/adapters/adxcg" "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" + "github.com/prebid/prebid-server/adapters/algorix" "github.com/prebid/prebid-server/adapters/amx" "github.com/prebid/prebid-server/adapters/applogy" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" "github.com/prebid/prebid-server/adapters/avocet" + "github.com/prebid/prebid-server/adapters/axonix" "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" "github.com/prebid/prebid-server/adapters/bidmachine" + "github.com/prebid/prebid-server/adapters/bidmyadz" + "github.com/prebid/prebid-server/adapters/bidscube" + "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/connectad" @@ -43,6 +48,7 @@ import ( "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -53,15 +59,18 @@ import ( "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/inmobi" + "github.com/prebid/prebid-server/adapters/interactiveoffers" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" + "github.com/prebid/prebid-server/adapters/kayzen" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/krushmedia" "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" + "github.com/prebid/prebid-server/adapters/madvertise" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/mobfoxpb" @@ -81,12 +90,14 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/silvermob" "github.com/prebid/prebid-server/adapters/smaato" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -117,114 +128,126 @@ import ( func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { return map[openrtb_ext.BidderName]adapters.Builder{ - openrtb_ext.Bidder33Across: ttx.Builder, - openrtb_ext.BidderAcuityAds: acuityads.Builder, - openrtb_ext.BidderAdf: adf.Builder, - openrtb_ext.BidderAdform: adform.Builder, - openrtb_ext.BidderAdgeneration: adgeneration.Builder, - openrtb_ext.BidderAdhese: adhese.Builder, - openrtb_ext.BidderAdkernel: adkernel.Builder, - openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, - openrtb_ext.BidderAdman: adman.Builder, - openrtb_ext.BidderAdmixer: admixer.Builder, - openrtb_ext.BidderAdOcean: adocean.Builder, - openrtb_ext.BidderAdoppler: adoppler.Builder, - openrtb_ext.BidderAdpone: adpone.Builder, - openrtb_ext.BidderAdot: adot.Builder, - openrtb_ext.BidderAdprime: adprime.Builder, - openrtb_ext.BidderAdtarget: adtarget.Builder, - openrtb_ext.BidderAdtelligent: adtelligent.Builder, - openrtb_ext.BidderAdvangelists: advangelists.Builder, - openrtb_ext.BidderAdxcg: adxcg.Builder, - openrtb_ext.BidderAdyoulike: adyoulike.Builder, - openrtb_ext.BidderAJA: aja.Builder, - openrtb_ext.BidderAMX: amx.Builder, - openrtb_ext.BidderApplogy: applogy.Builder, - openrtb_ext.BidderAppnexus: appnexus.Builder, - openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, - openrtb_ext.BidderAvocet: avocet.Builder, - openrtb_ext.BidderBeachfront: beachfront.Builder, - openrtb_ext.BidderBeintoo: beintoo.Builder, - openrtb_ext.BidderBetween: between.Builder, - openrtb_ext.BidderBidmachine: bidmachine.Builder, - openrtb_ext.BidderBrightroll: brightroll.Builder, - openrtb_ext.BidderColossus: colossus.Builder, - openrtb_ext.BidderConnectAd: connectad.Builder, - openrtb_ext.BidderConsumable: consumable.Builder, - openrtb_ext.BidderConversant: conversant.Builder, - openrtb_ext.BidderCpmstar: cpmstar.Builder, - openrtb_ext.BidderCriteo: criteo.Builder, - openrtb_ext.BidderDatablocks: datablocks.Builder, - openrtb_ext.BidderDecenterAds: decenterads.Builder, - openrtb_ext.BidderDeepintent: deepintent.Builder, - openrtb_ext.BidderDmx: dmx.Builder, - openrtb_ext.BidderEmxDigital: emx_digital.Builder, - openrtb_ext.BidderEngageBDR: engagebdr.Builder, - openrtb_ext.BidderEPlanning: eplanning.Builder, - openrtb_ext.BidderEpom: epom.Builder, - openrtb_ext.BidderGamma: gamma.Builder, - openrtb_ext.BidderGamoshi: gamoshi.Builder, - openrtb_ext.BidderGrid: grid.Builder, - openrtb_ext.BidderGumGum: gumgum.Builder, - openrtb_ext.BidderImprovedigital: improvedigital.Builder, - openrtb_ext.BidderInMobi: inmobi.Builder, - openrtb_ext.BidderInvibes: invibes.Builder, - openrtb_ext.BidderIx: ix.Builder, - openrtb_ext.BidderJixie: jixie.Builder, - openrtb_ext.BidderKidoz: kidoz.Builder, - openrtb_ext.BidderKrushmedia: krushmedia.Builder, - openrtb_ext.BidderKubient: kubient.Builder, - openrtb_ext.BidderLockerDome: lockerdome.Builder, - openrtb_ext.BidderLogicad: logicad.Builder, - openrtb_ext.BidderLunaMedia: lunamedia.Builder, - openrtb_ext.BidderMarsmedia: marsmedia.Builder, - openrtb_ext.BidderMediafuse: adtelligent.Builder, - openrtb_ext.BidderMgid: mgid.Builder, - openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, - openrtb_ext.BidderMobileFuse: mobilefuse.Builder, - openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, - openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, - openrtb_ext.BidderNoBid: nobid.Builder, - openrtb_ext.BidderOneTag: onetag.Builder, - openrtb_ext.BidderOpenx: openx.Builder, - openrtb_ext.BidderOrbidder: orbidder.Builder, - openrtb_ext.BidderOutbrain: outbrain.Builder, - openrtb_ext.BidderPangle: pangle.Builder, - openrtb_ext.BidderPubmatic: pubmatic.Builder, - openrtb_ext.BidderPubnative: pubnative.Builder, - openrtb_ext.BidderPulsepoint: pulsepoint.Builder, - openrtb_ext.BidderRevcontent: revcontent.Builder, - openrtb_ext.BidderRhythmone: rhythmone.Builder, - openrtb_ext.BidderRTBHouse: rtbhouse.Builder, - openrtb_ext.BidderRubicon: rubicon.Builder, - openrtb_ext.BidderSharethrough: sharethrough.Builder, - openrtb_ext.BidderSilverMob: silvermob.Builder, - openrtb_ext.BidderSmaato: smaato.Builder, - openrtb_ext.BidderSmartAdserver: smartadserver.Builder, - openrtb_ext.BidderSmartRTB: smartrtb.Builder, - openrtb_ext.BidderSmartyAds: smartyads.Builder, - openrtb_ext.BidderSomoaudience: somoaudience.Builder, - openrtb_ext.BidderSonobi: sonobi.Builder, - openrtb_ext.BidderSovrn: sovrn.Builder, - openrtb_ext.BidderSpotX: spotx.Builder, - openrtb_ext.BidderSynacormedia: synacormedia.Builder, - openrtb_ext.BidderTappx: tappx.Builder, - openrtb_ext.BidderTelaria: telaria.Builder, - openrtb_ext.BidderTriplelift: triplelift.Builder, - openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, - openrtb_ext.BidderTrustX: grid.Builder, - openrtb_ext.BidderUcfunnel: ucfunnel.Builder, - openrtb_ext.BidderUnicorn: unicorn.Builder, - openrtb_ext.BidderUnruly: unruly.Builder, - openrtb_ext.BidderValueImpression: valueimpression.Builder, - openrtb_ext.BidderVASTBidder: vastbidder.Builder, - openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, - openrtb_ext.BidderVisx: visx.Builder, - openrtb_ext.BidderVrtcal: vrtcal.Builder, - openrtb_ext.BidderYeahmobi: yeahmobi.Builder, - openrtb_ext.BidderYieldlab: yieldlab.Builder, - openrtb_ext.BidderYieldmo: yieldmo.Builder, - openrtb_ext.BidderYieldone: yieldone.Builder, - openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, + openrtb_ext.Bidder33Across: ttx.Builder, + openrtb_ext.BidderAcuityAds: acuityads.Builder, + openrtb_ext.BidderAdf: adf.Builder, + openrtb_ext.BidderAdform: adform.Builder, + openrtb_ext.BidderAdgeneration: adgeneration.Builder, + openrtb_ext.BidderAdhese: adhese.Builder, + openrtb_ext.BidderAdkernel: adkernel.Builder, + openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, + openrtb_ext.BidderAdman: adman.Builder, + openrtb_ext.BidderAdmixer: admixer.Builder, + openrtb_ext.BidderAdOcean: adocean.Builder, + openrtb_ext.BidderAdoppler: adoppler.Builder, + openrtb_ext.BidderAdpone: adpone.Builder, + openrtb_ext.BidderAdot: adot.Builder, + openrtb_ext.BidderAdprime: adprime.Builder, + openrtb_ext.BidderAdtarget: adtarget.Builder, + openrtb_ext.BidderAdtelligent: adtelligent.Builder, + openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdxcg: adxcg.Builder, + openrtb_ext.BidderAdyoulike: adyoulike.Builder, + openrtb_ext.BidderAJA: aja.Builder, + openrtb_ext.BidderAlgorix: algorix.Builder, + openrtb_ext.BidderAMX: amx.Builder, + openrtb_ext.BidderApplogy: applogy.Builder, + openrtb_ext.BidderAppnexus: appnexus.Builder, + openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, + openrtb_ext.BidderAvocet: avocet.Builder, + openrtb_ext.BidderAxonix: axonix.Builder, + openrtb_ext.BidderBeachfront: beachfront.Builder, + openrtb_ext.BidderBeintoo: beintoo.Builder, + openrtb_ext.BidderBetween: between.Builder, + openrtb_ext.BidderBidmachine: bidmachine.Builder, + openrtb_ext.BidderBidmyadz: bidmyadz.Builder, + openrtb_ext.BidderBidsCube: bidscube.Builder, + openrtb_ext.BidderBmtm: bmtm.Builder, + openrtb_ext.BidderBrightroll: brightroll.Builder, + openrtb_ext.BidderColossus: colossus.Builder, + openrtb_ext.BidderConnectAd: connectad.Builder, + openrtb_ext.BidderConsumable: consumable.Builder, + openrtb_ext.BidderConversant: conversant.Builder, + openrtb_ext.BidderCpmstar: cpmstar.Builder, + openrtb_ext.BidderCriteo: criteo.Builder, + openrtb_ext.BidderDatablocks: datablocks.Builder, + openrtb_ext.BidderDecenterAds: decenterads.Builder, + openrtb_ext.BidderDeepintent: deepintent.Builder, + openrtb_ext.BidderDmx: dmx.Builder, + openrtb_ext.BidderEmxDigital: emx_digital.Builder, + openrtb_ext.BidderEngageBDR: engagebdr.Builder, + openrtb_ext.BidderEPlanning: eplanning.Builder, + openrtb_ext.BidderEpom: epom.Builder, + openrtb_ext.BidderEVolution: evolution.Builder, + openrtb_ext.BidderGamma: gamma.Builder, + openrtb_ext.BidderGamoshi: gamoshi.Builder, + openrtb_ext.BidderGrid: grid.Builder, + openrtb_ext.BidderGumGum: gumgum.Builder, + openrtb_ext.BidderImprovedigital: improvedigital.Builder, + openrtb_ext.BidderInMobi: inmobi.Builder, + openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, + openrtb_ext.BidderInvibes: invibes.Builder, + openrtb_ext.BidderIx: ix.Builder, + openrtb_ext.BidderJixie: jixie.Builder, + openrtb_ext.BidderKayzen: kayzen.Builder, + openrtb_ext.BidderKidoz: kidoz.Builder, + openrtb_ext.BidderKrushmedia: krushmedia.Builder, + openrtb_ext.BidderKubient: kubient.Builder, + openrtb_ext.BidderLockerDome: lockerdome.Builder, + openrtb_ext.BidderLogicad: logicad.Builder, + openrtb_ext.BidderLunaMedia: lunamedia.Builder, + openrtb_ext.BidderSaLunaMedia: salunamedia.Builder, + openrtb_ext.BidderMadvertise: madvertise.Builder, + openrtb_ext.BidderMarsmedia: marsmedia.Builder, + openrtb_ext.BidderMediafuse: adtelligent.Builder, + openrtb_ext.BidderMgid: mgid.Builder, + openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, + openrtb_ext.BidderMobileFuse: mobilefuse.Builder, + openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, + openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, + openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOneTag: onetag.Builder, + openrtb_ext.BidderOpenx: openx.Builder, + openrtb_ext.BidderOrbidder: orbidder.Builder, + openrtb_ext.BidderOutbrain: outbrain.Builder, + openrtb_ext.BidderPangle: pangle.Builder, + openrtb_ext.BidderPubmatic: pubmatic.Builder, + openrtb_ext.BidderPubnative: pubnative.Builder, + openrtb_ext.BidderPulsepoint: pulsepoint.Builder, + openrtb_ext.BidderRevcontent: revcontent.Builder, + openrtb_ext.BidderRhythmone: rhythmone.Builder, + openrtb_ext.BidderRTBHouse: rtbhouse.Builder, + openrtb_ext.BidderRubicon: rubicon.Builder, + openrtb_ext.BidderSharethrough: sharethrough.Builder, + openrtb_ext.BidderSilverMob: silvermob.Builder, + openrtb_ext.BidderSmaato: smaato.Builder, + openrtb_ext.BidderSmartAdserver: smartadserver.Builder, + openrtb_ext.BidderSmartRTB: smartrtb.Builder, + openrtb_ext.BidderSmartyAds: smartyads.Builder, + openrtb_ext.BidderSmileWanted: smilewanted.Builder, + openrtb_ext.BidderSomoaudience: somoaudience.Builder, + openrtb_ext.BidderSonobi: sonobi.Builder, + openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderSpotX: spotx.Builder, + openrtb_ext.BidderSynacormedia: synacormedia.Builder, + openrtb_ext.BidderTappx: tappx.Builder, + openrtb_ext.BidderTelaria: telaria.Builder, + openrtb_ext.BidderTriplelift: triplelift.Builder, + openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, + openrtb_ext.BidderTrustX: grid.Builder, + openrtb_ext.BidderUcfunnel: ucfunnel.Builder, + openrtb_ext.BidderUnicorn: unicorn.Builder, + openrtb_ext.BidderUnruly: unruly.Builder, + openrtb_ext.BidderVASTBidder: vastbidder.Builder, + openrtb_ext.BidderValueImpression: valueimpression.Builder, + openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, + openrtb_ext.BidderViewdeos: adtelligent.Builder, + openrtb_ext.BidderVisx: visx.Builder, + openrtb_ext.BidderVrtcal: vrtcal.Builder, + openrtb_ext.BidderYeahmobi: yeahmobi.Builder, + openrtb_ext.BidderYieldlab: yieldlab.Builder, + openrtb_ext.BidderYieldmo: yieldmo.Builder, + openrtb_ext.BidderYieldone: yieldone.Builder, + openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/exchange/exchange.go b/exchange/exchange.go index d670c504db7..a3e17361183 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -52,19 +52,19 @@ type IdFetcher interface { } type exchange struct { - adapterMap map[openrtb_ext.BidderName]adaptedBidder - bidderInfo config.BidderInfos - me metrics.MetricsEngine - cache prebid_cache_client.Client - cacheTime time.Duration - gDPR gdpr.Permissions - currencyConverter *currency.RateConverter - externalURL string - UsersyncIfAmbiguous bool - privacyConfig config.Privacy - categoriesFetcher stored_requests.CategoryFetcher - bidIDGenerator BidIDGenerator - trakerURL string + adapterMap map[openrtb_ext.BidderName]adaptedBidder + bidderInfo config.BidderInfos + me metrics.MetricsEngine + cache prebid_cache_client.Client + cacheTime time.Duration + gDPR gdpr.Permissions + currencyConverter *currency.RateConverter + externalURL string + gdprDefaultValue gdpr.Signal + privacyConfig config.Privacy + categoriesFetcher stored_requests.CategoryFetcher + bidIDGenerator BidIDGenerator + trakerURL string } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -101,18 +101,33 @@ func (big *bidIDGenerator) New() (string, error) { return rawUuid.String(), err } +type deduplicateChanceGenerator interface { + Generate() bool +} + +type randomDeduplicateBidBooleanGenerator struct{} + +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, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { + gdprDefaultValue := gdpr.SignalYes + if cfg.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ - adapterMap: adapters, - bidderInfo: infos, - cache: cache, - cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, - categoriesFetcher: categoriesFetcher, - currencyConverter: currencyConverter, - externalURL: cfg.ExternalURL, - gDPR: gDPR, - me: metricsEngine, - UsersyncIfAmbiguous: cfg.GDPR.UsersyncIfAmbiguous, + adapterMap: adapters, + bidderInfo: infos, + cache: cache, + cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, + categoriesFetcher: categoriesFetcher, + currencyConverter: currencyConverter, + externalURL: cfg.ExternalURL, + gDPR: gDPR, + me: metricsEngine, + gdprDefaultValue: gdprDefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -179,10 +194,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * recordImpMetrics(r.BidRequest, e.me) // Make our best guess if GDPR applies - usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) + gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, usersyncIfAmbiguous, e.privacyConfig, &r.Account) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) @@ -214,7 +229,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * //If includebrandcategory is present in ext then CE feature is on. if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -300,8 +315,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool { - usersyncIfAmbiguous := e.UsersyncIfAmbiguous +func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal { + gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { @@ -313,14 +328,14 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) boo // If we have a country set, and it is on the list, we assume GDPR applies if not set on the request. // Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long). if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found { - usersyncIfAmbiguous = false + gdprDefaultValue = gdpr.SignalYes } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - usersyncIfAmbiguous = true + gdprDefaultValue = gdpr.SignalNo } } - return usersyncIfAmbiguous + return gdprDefaultValue } func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { @@ -648,7 +663,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -791,7 +806,7 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, currBidPrice = 0 } if dupeBidPrice == currBidPrice { - if rand.Intn(100) < 50 { + if booleanGenerator.Generate() { dupeBidPrice = -1 } else { currBidPrice = -1 @@ -806,11 +821,16 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, } else { // An older bid from a different seatBid we've already finished with oldSeatBid := (seatBids)[dupe.bidderName] + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") if len(oldSeatBid.bids) == 1 { seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) - rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { - oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) + // This is a very rare, but still possible case where bid needs to be removed from already processed bidder + // This happens when current processing bidder has a bid that has same deduplication key as a bid from already processed bidder + // and already processed bid was selected to be removed + // See example of input data in unit test `TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice` + // Need to remove bid by name, not index in this case + removeBidById(oldSeatBid, dupe.bidID) } } delete(res, dupe.bidID) @@ -821,9 +841,9 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, continue } } - dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } res[bidID] = categoryDuration + dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } if len(bidsToRemove) > 0 { @@ -849,6 +869,24 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, return res, seatBids, rejections, nil } +func removeBidById(seatBid *pbsOrtbSeatBid, bidID string) { + //Find index of bid to remove + dupeBidIndex := -1 + for i, bid := range seatBid.bids { + if bid.bid.ID == bidID { + dupeBidIndex = i + break + } + } + if dupeBidIndex != -1 { + if dupeBidIndex < len(seatBid.bids)-1 { + seatBid.bids = append(seatBid.bids[:dupeBidIndex], seatBid.bids[dupeBidIndex+1:]...) + } else if dupeBidIndex == len(seatBid.bids)-1 { + seatBid.bids = seatBid.bids[:len(seatBid.bids)-1] + } + } +} + func updateRejections(rejections []string, bidID string, reason string) []string { message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason) return append(rejections, message) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 48b77bb2725..9932e865b8b 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -32,6 +32,7 @@ import ( "github.com/buger/jsonparser" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" ) @@ -87,8 +88,8 @@ func TestNewExchange(t *testing.T) { // 4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... ) // 5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns func TestCharacterEscape(t *testing.T) { - /* 1) Adapter with a '& char in its endpoint property */ - /* https://github.com/prebid/prebid-server/issues/465 */ + // 1) Adapter with a '& char in its endpoint property + // https://github.com/prebid/prebid-server/issues/465 cfg := &config.Configuration{ Adapters: make(map[string]config.Adapter, 1), } @@ -96,7 +97,7 @@ func TestCharacterEscape(t *testing.T) { Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2", //Note the '&' character in there } - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration //Other parameters also needed to create exchange handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) @@ -115,7 +116,7 @@ func TestCharacterEscape(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -148,10 +149,10 @@ func TestCharacterEscape(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList) - /* 5) Assert we have no errors and one '&' character as we are supposed to */ + // 5) Assert we have no errors and one '&' character as we are supposed to if err != nil { t.Errorf("exchange.buildBidResponse returned unexpected error: %v", err) } @@ -538,6 +539,432 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } +func TestOverrideWithCustomCurrency(t *testing.T) { + + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + + type testIn struct { + customCurrencyRates json.RawMessage + bidRequestCurrency string + } + type testResults struct { + numBids int + bidRespPrice float64 + bidRespCurrency string + } + + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "Blank currency field in ext. bidRequest comes with a valid currency but conversion rate was not found in PBS. Return no bids", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ "prebid": { "currency": {} } } `), + bidRequestCurrency: "GBP", + }, + expected: testResults{}, + }, + { + desc: "valid request.ext.prebid.currency, expect custom rates to override those of the currency rate server", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 20.00, + "EUR": 10.95 + } + } + } + } + }`), + bidRequestCurrency: "MXN", + }, + expected: testResults{ + numBids: 1, + bidRespPrice: 20.00, + bidRespCurrency: "MXN", + }, + }, + } + + // Init mock currency conversion service + mockCurrencyConverter.Run() + + // Init an exchange to run an auction from + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + mockAppnexusBidService := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer mockAppnexusBidService.Close() + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + oneDollarBidBidder := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: mockAppnexusBidService.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + } + + e := new(exchange) + e.cache = &wellBehavedCache{} + e.me = &metricsConf.DummyMetricsEngine{} + e.gDPR = gdpr.AlwaysAllow{} + e.currencyConverter = mockCurrencyConverter + e.categoriesFetcher = categoriesFetcher + e.bidIDGenerator = &mockBidIDGenerator{false, false} + + // 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}`)}, + } + + // Run tests + for _, test := range testCases { + + oneDollarBidBidder.bidResponse = &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{Price: 1.00}, + }, + }, + Currency: "USD", + } + + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + } + + // Set custom rates in extension + mockBidRequest.Ext = test.in.customCurrencyRates + + // Set bidRequest currency list + mockBidRequest.Cur = []string{test.in.bidRequestCurrency} + + auctionRequest := AuctionRequest{ + BidRequest: mockBidRequest, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + + // Assertions + assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) + + if test.expected.numBids > 0 { + // Assert out currency + assert.Equal(t, test.expected.bidRespCurrency, outBidResponse.Cur, "Bid response currency is wrong: %s \n", test.desc) + + // Assert returned bid + if !assert.NotNil(t, outBidResponse, "outBidResponse is nil: %s \n", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid, "outBidResponse.SeatBid is empty: %s", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "outBidResponse.SeatBid[0].Bid is empty: %s", test.desc) { + return + } + + // Assert returned bid price matches the currency conversion + assert.Equal(t, test.expected.bidRespPrice, outBidResponse.SeatBid[0].Bid[0].Price, "Bid response seatBid price is wrong: %s", test.desc) + } else { + assert.Len(t, outBidResponse.SeatBid, 0, "outBidResponse.SeatBid should be empty: %s", test.desc) + } + } +} + +func TestAdapterCurrency(t *testing.T) { + fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + currencyConverter := currency.NewRateConverter( + fakeCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + currencyConverter.Run() + + // Initialize Mock Bidder + // - Response purposefully causes PBS-Core to stop processing the request, since this test is only + // interested in the call to MakeRequests and nothing after. + mockBidder := &mockBidder{} + mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil)) + + // Initialize Real Exchange + e := exchange{ + cache: &wellBehavedCache{}, + me: &metricsConf.DummyMetricsEngine{}, + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currencyConverter, + categoriesFetcher: nilCategoryFetcher{}, + bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderName("foo"), nil), + }, + } + + // Define Bid Request + request := &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(`{"foo": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":0}`), + }, + Cur: []string{"USD"}, + Ext: json.RawMessage(`{"prebid": {"currency": {"rates": {"USD": {"MXN": 20.00}}}}}`), + } + + // Run Auction + auctionRequest := AuctionRequest{ + BidRequest: request, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + assert.NoError(t, err) + assert.Equal(t, "some-request-id", response.ID, "Response ID") + assert.Empty(t, response.SeatBid, "Response Bids") + assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") + + // Test Currency Converter Properly Passed To Adapter + if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { + converted, err := mockBidder.lastExtraRequestInfo.ConvertCurrency(2.0, "USD", "MXN") + assert.NoError(t, err, "Currency Conversion Error") + assert.Equal(t, 40.0, converted, "Currency Conversion Response") + } +} + +func TestGetAuctionCurrencyRates(t *testing.T) { + + pbsRates := map[string]map[string]float64{ + "MXN": { + "USD": 20.13, + "EUR": 27.82, + "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates + }, + } + + customRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // different rate than in pbsRates + "EUR": 27.82, // same as in pbsRates + "GBP": 31.12, // not found in pbsRates at all + }, + } + + expectedRateEngineRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // rates engine will prioritize the value found in custom rates + "EUR": 27.82, // same value in both the engine reads the custom entry first + "JPY": 5.09, // the engine will find it in the pbsRates conversions + "GBP": 31.12, // the engine will find it in the custom conversions + }, + } + + boolTrue := true + boolFalse := false + + type testInput struct { + pbsRates map[string]map[string]float64 + bidExtCurrency *openrtb_ext.ExtRequestCurrency + } + type testOutput struct { + constantRates bool + resultingRates map[string]map[string]float64 + } + testCases := []struct { + desc string + given testInput + expected testOutput + }{ + { + "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: expectedRateEngineRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: nil, + }, + testOutput{ + resultingRates: pbsRates, + }, + }, + { + "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: nil, + }, + testOutput{ + constantRates: true, + }, + }, + } + + for _, tc := range testCases { + + // Test setup: + jsonPbsRates, err := json.Marshal(tc.given.pbsRates) + if err != nil { + t.Fatalf("Failed to marshal PBS rates: %v", err) + } + + // Init mock currency conversion service + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + mockCurrencyConverter.Run() + + e := new(exchange) + e.currencyConverter = mockCurrencyConverter + + // Run test + auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency) + + // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected + rate, err := auctionRates.GetRate("USD", "USD") + assert.NoError(t, err, tc.desc) + assert.Equal(t, float64(1), rate, tc.desc) + + // If we expect an error, assert we have one along with a conversion rate of zero + if tc.expected.constantRates { + rate, err := auctionRates.GetRate("USD", "MXN") + assert.Error(t, err, tc.desc) + assert.Equal(t, float64(0), rate, tc.desc) + } else { + for fromCurrency, rates := range tc.expected.resultingRates { + for toCurrency, expectedRate := range rates { + actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) + assert.NoError(t, err, tc.desc) + assert.Equal(t, expectedRate, actualRate, tc.desc) + } + } + } + } +} + func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -740,7 +1167,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { testExternalCacheHost := "www.externalprebidcache.net" testExternalCachePath := "endpoints/cache" - /* 1) An adapter */ + // 1) An adapter bidderName := openrtb_ext.BidderName("appnexus") cfg := &config.Configuration{ @@ -761,7 +1188,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterList := make([]openrtb_ext.BidderName, 0, 2) testEngine := metricsConf.NewMetricsEngine(cfg, adapterList) - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() @@ -778,7 +1205,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine) e := NewExchange(adapters, pbc, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} //adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, @@ -880,10 +1307,10 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList) - /* 5) Assert we have no errors and the bid response we expected*/ + // 5) Assert we have no errors and the bid response we expected assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") expectedBidResponse := &openrtb2.BidResponse{ @@ -1843,6 +2270,14 @@ func (big *mockBidIDGenerator) New() (string, error) { } +type fakeRandomDeduplicateBidBooleanGenerator struct { + returnValue bool +} + +func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool { + return m.returnValue +} + func newExtRequest() openrtb_ext.ExtRequest { priceGran := openrtb_ext.PriceGranularity{ Precision: 2, @@ -1943,7 +2378,7 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1999,7 +2434,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2052,7 +2487,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2135,7 +2570,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2206,7 +2641,7 @@ func TestCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") @@ -2287,7 +2722,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -2353,7 +2788,7 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2408,7 +2843,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2508,9 +2943,9 @@ func TestBidRejectionErrors(t *testing.T) { seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} adapterBids[bidderName] = &seatBid - bidRequest := openrtb2.BidRequest{} - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData) + + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -2574,7 +3009,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -2593,9 +3028,171 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { } else { assert.Nil(t, seatBidApn1.bids, "Appnexus_1 seat bid should not have any bids back") assert.Len(t, seatBidApn2.bids, 1, "Appnexus_2 seat bid should have only one back") + } + } +} + +func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) { + // This test covers a very rare de-duplication case where bid needs to be removed from already processed bidder + // This happens when current processing bidder has a bid that has same de-duplication key as a bid from already processed bidder + // and already processed bid was selected to be removed + + //In this test case bids bid_idApn1_1 and bid_idApn1_2 will be removed due to hardcoded "fakeRandomDeduplicateBidBooleanGenerator{true}" + + // Also there are should be more than one bids in bidder to test how we remove single element from bids array. + // In case there is just one bid to remove - we remove the entire bidder. + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + bidRequest := openrtb2.BidRequest{} + requestExt := newExtRequestTranslateCategories(nil) + + targData := &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: true, + } + + requestExt.Prebid.Targeting.DurationRangeSec = []int{30} + requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false + + cats1 := []string{"IAB1-3"} + cats2 := []string{"IAB1-4"} + + bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1} + + bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1} + bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1} + + bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + + bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + innerBidsApn1 := []*pbsOrtbBid{ + &bid1_Apn1_1, + &bid1_Apn1_2, + } + + innerBidsApn2 := []*pbsOrtbBid{ + &bid1_Apn2_1, + &bid1_Apn2_2, + } + + adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + + seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} + bidderNameApn1 := openrtb_ext.BidderName("appnexus1") + + seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"} + bidderNameApn2 := openrtb_ext.BidderName("appnexus2") + + adapterBids[bidderNameApn1] = &seatBidApn1 + adapterBids[bidderNameApn2] = &seatBidApn2 + + _, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}) + + assert.NoError(t, err, "Category mapping error should be empty") + + //Total number of bids from all bidders in this case should be 2 + bidsFromFirstBidder := adapterBids[bidderNameApn1] + bidsFromSecondBidder := adapterBids[bidderNameApn2] + + totalNumberOfbids := 0 + + //due to random map order we need to identify what bidder was first + firstBidderIndicator := true + + if bidsFromFirstBidder.bids != nil { + totalNumberOfbids += len(bidsFromFirstBidder.bids) + } + + if bidsFromSecondBidder.bids != nil { + firstBidderIndicator = false + totalNumberOfbids += len(bidsFromSecondBidder.bids) + } + + assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned") + assert.Len(t, rejections, 2, "2 bids should be de-duplicated") + + if firstBidderIndicator { + assert.Len(t, adapterBids[bidderNameApn1].bids, 2) + assert.Len(t, adapterBids[bidderNameApn2].bids, 0) + + assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].bids[1].bid.ID, "Incorrect expected bid 2 id") + + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } else { + assert.Len(t, adapterBids[bidderNameApn1].bids, 0) + assert.Len(t, adapterBids[bidderNameApn2].bids, 2) + + assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id") + + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } +} + +func TestRemoveBidById(t *testing.T) { + cats1 := []string{"IAB1-3"} + + bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1} + + bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + + type aTest struct { + desc string + inBidName string + outBids []*pbsOrtbBid + } + testCases := []aTest{ + { + desc: "remove element from the middle", + inBidName: "bid_idApn1_2", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3}, + }, + { + desc: "remove element from the end", + inBidName: "bid_idApn1_3", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2}, + }, + { + desc: "remove element from the beginning", + inBidName: "bid_idApn1_1", + outBids: []*pbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3}, + }, + { + desc: "remove element that doesn't exist", + inBidName: "bid_idApn", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3}, + }, + } + for _, test := range testCases { + + innerBidsApn1 := []*pbsOrtbBid{ + &bid1_Apn1_1, + &bid1_Apn1_2, + &bid1_Apn1_3, } + seatBidApn1 := &pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} + + removeBidById(seatBidApn1, test.inBidName) + assert.Len(t, seatBidApn1.bids, len(test.outBids), test.desc) + assert.ElementsMatch(t, seatBidApn1.bids, test.outBids, "Incorrect bids in response") } } @@ -3136,6 +3733,36 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } +// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type fakeCurrencyRatesHttpClient struct { + responseBody string +} + +func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + }, nil +} + +type mockBidder struct { + mock.Mock + lastExtraRequestInfo *adapters.ExtraRequestInfo +} + +func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + m.lastExtraRequestInfo = reqInfo + + args := m.Called(request, reqInfo) + return args.Get(0).([]*adapters.RequestData), args.Get(1).([]error) +} + +func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + args := m.Called(internalRequest, externalRequest, response) + return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error) +} + //TestApplyAdvertiserBlocking verifies advertiser blocking //Currently it is expected to work only with TagBidders and not woth // normal bidders @@ -3691,47 +4318,3 @@ func newTestRtbAdapter(name string) *bidderAdapter { BidderName: openrtb_ext.BidderName(name), } } - -func TestRecordAdaptorDuplicateBidIDs(t *testing.T) { - type bidderCollisions = map[string]int - testCases := []struct { - scenario string - bidderCollisions *bidderCollisions // represents no of collisions detected for bid.id at bidder level for given request - hasCollision bool - }{ - {scenario: "invalid collision value", bidderCollisions: &map[string]int{"bidder-1": -1}, hasCollision: false}, - {scenario: "no collision", bidderCollisions: &map[string]int{"bidder-1": 0}, hasCollision: false}, - {scenario: "one collision", bidderCollisions: &map[string]int{"bidder-1": 1}, hasCollision: false}, - {scenario: "multiple collisions", bidderCollisions: &map[string]int{"bidder-1": 2}, hasCollision: true}, // when 2 collisions it counter will be 1 - {scenario: "multiple bidders", bidderCollisions: &map[string]int{"bidder-1": 2, "bidder-2": 4}, hasCollision: true}, - {scenario: "multiple bidders with bidder-1 no collision", bidderCollisions: &map[string]int{"bidder-1": 1, "bidder-2": 4}, hasCollision: true}, - {scenario: "no bidders", bidderCollisions: nil, hasCollision: false}, - } - testEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, nil) - - for _, testcase := range testCases { - var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid - if nil == testcase.bidderCollisions { - break - } - adapterBids = make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) - for bidder, collisions := range *testcase.bidderCollisions { - bids := make([]*pbsOrtbBid, 0) - testBidID := "bid_id_for_bidder_" + bidder - // add bids as per collisions value - bidCount := 0 - for ; bidCount < collisions; bidCount++ { - bids = append(bids, &pbsOrtbBid{ - bid: &openrtb2.Bid{ - ID: testBidID, - }, - }) - } - if nil == adapterBids[openrtb_ext.BidderName(bidder)] { - adapterBids[openrtb_ext.BidderName(bidder)] = new(pbsOrtbSeatBid) - } - adapterBids[openrtb_ext.BidderName(bidder)].bids = bids - } - assert.Equal(t, testcase.hasCollision, recordAdaptorDuplicateBidIDs(testEngine, adapterBids)) - } -} diff --git a/exchange/legacy.go b/exchange/legacy.go deleted file mode 100644 index 0e7d1590686..00000000000 --- a/exchange/legacy.go +++ /dev/null @@ -1,375 +0,0 @@ -package exchange - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" -) - -// AdaptLegacyAdapter turns a bidder.Adapter into an adaptedBidder. -// -// This is a temporary function which helps make the transition to OpenRTB smooth. Bidders which have not been -// updated yet can use this to be "OpenRTB-ish". They'll bid as well as they can, given the limitations of the -// legacy protocol -func adaptLegacyAdapter(adapter adapters.Adapter) adaptedBidder { - return &adaptedAdapter{ - adapter: adapter, - } -} - -type adaptedAdapter struct { - adapter adapters.Adapter -} - -// requestBid attempts to bid on OpenRTB requests using the legacy protocol. -// -// This is not ideal. OpenRTB provides a superset of the legacy data structures. -// For requests which use those features, the best we can do is respond with "no bid". -func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name) - if legacyRequest == nil || legacyBidder == nil { - return nil, errs - } - - legacyBids, err := bidder.adapter.Call(ctx, legacyRequest, legacyBidder) - if err != nil { - errs = append(errs, err) - } - - for i := 0; i < len(legacyBids); i++ { - legacyBids[i].Price = legacyBids[i].Price * bidAdjustment - } - - finalResponse, moreErrs := toNewResponse(legacyBids, legacyBidder, name) - return finalResponse, append(errs, moreErrs...) -} - -// ---------------------------------------------------------------------------- -// Request transformations. - -// toLegacyAdapterInputs is a best-effort transformation of an OpenRTB BidRequest into the args needed to run a legacy Adapter. -// If the OpenRTB request is too complex, it fails with an error. -// If the error is nil, then the PBSRequest and PBSBidder are valid. -func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { - legacyReq, err := bidder.toLegacyRequest(req) - if err != nil { - return nil, nil, []error{err} - } - - legacyBidder, errs := toLegacyBidder(req, name) - if legacyBidder == nil { - return nil, nil, errs - } - - return legacyReq, legacyBidder, errs -} - -func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb2.BidRequest) (*pbs.PBSRequest, error) { - acctId, err := toAccountId(req) - if err != nil { - return nil, err - } - - tId, err := toTransactionId(req) - if err != nil { - return nil, err - } - - isSecure, err := toSecure(req) - if err != nil { - return nil, err - } - - isDebug := false - var requestExt openrtb_ext.ExtRequest - if req.Ext != nil { - err = json.Unmarshal(req.Ext, &requestExt) - if err != nil { - return nil, fmt.Errorf("Error decoding Request.ext : %s", err.Error()) - } - } - - if requestExt.Prebid.Debug { - isDebug = true - } - - url := "" - domain := "" - if req.Site != nil { - url = req.Site.Page - domain = req.Site.Domain - } - - cookie := usersync.NewPBSCookie() - if req.User != nil { - if req.User.BuyerUID != "" { - cookie.TrySync(bidder.adapter.Name(), req.User.BuyerUID) - } - - // This shouldn't be appnexus-specific... but this line does correctly invert the - // logic from adapters/openrtb_util.go, which will preserve this questionable behavior in legacy adapters. - if req.User.ID != "" { - cookie.TrySync("adnxs", req.User.ID) - } - } - - return &pbs.PBSRequest{ - AccountID: acctId, - Tid: tId, - // CacheMarkup is excluded because no legacy adapters read from it - // SortBids is excluded because no legacy adapters read from it - // MaxKeyLength is excluded because no legacy adapters read from it - Secure: isSecure, - TimeoutMillis: req.TMax, - // AdUnits is excluded because no legacy adapters read from it - IsDebug: isDebug, - App: req.App, - Device: req.Device, - // PBSUser is excluded because rubicon is the only adapter which reads from it, and they're supporting OpenRTB directly - // SDK is excluded because that information doesn't exist in openrtb2. - // Bidders is excluded because no legacy adapters read from it - User: req.User, - Cookie: cookie, - Url: url, - Domain: domain, - // Start is excluded because no legacy adapters read from it - Regs: req.Regs, - }, nil -} - -func toAccountId(req *openrtb2.BidRequest) (string, error) { - if req.Site != nil && req.Site.Publisher != nil { - return req.Site.Publisher.ID, nil - } - if req.App != nil && req.App.Publisher != nil { - return req.App.Publisher.ID, nil - } - return "", errors.New("bidrequest.site.publisher.id or bidrequest.app.publisher.id required for legacy bidders.") -} - -func toTransactionId(req *openrtb2.BidRequest) (string, error) { - if req.Source != nil { - return req.Source.TID, nil - } - return "", errors.New("bidrequest.source.tid required for legacy bidders.") -} - -func toSecure(req *openrtb2.BidRequest) (secure int8, err error) { - secure = -1 - for _, imp := range req.Imp { - if imp.Secure != nil { - thisVal := *imp.Secure - if thisVal == 0 { - if secure == 1 { - err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.") - return - } - secure = 0 - } else if thisVal == 1 { - if secure == 0 { - err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.") - return - } - secure = 1 - } - } - } - if secure == -1 { - secure = 0 - } - - return -} - -func toLegacyBidder(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { - adUnits, errs := toPBSAdUnits(req) - if len(adUnits) > 0 { - return &pbs.PBSBidder{ - BidderCode: string(name), - // AdUnitCode is excluded because no legacy adapters read from it - // ResponseTime is excluded because no legacy adapters read from it - // NumBids is excluded because no legacy adapters read from it - // Error is excluded because no legacy adapters read from it - // NoCookie is excluded because no legacy adapters read from it - // NoBid is excluded because no legacy adapters read from it - // UsersyncInfo is excluded because no legacy adapters read from it - // Debug is excluded because legacy adapters only use it in nil-safe ways. - // They *do* write to it, though, so it may be read when unpacking the response. - AdUnits: adUnits, - }, errs - } else { - return nil, errs - } -} - -func toPBSAdUnits(req *openrtb2.BidRequest) ([]pbs.PBSAdUnit, []error) { - adUnits := make([]pbs.PBSAdUnit, len(req.Imp)) - var errs []error = nil - nextAdUnit := 0 - for i := 0; i < len(req.Imp); i++ { - err := initPBSAdUnit(&(req.Imp[i]), &(adUnits[nextAdUnit])) - if err != nil { - errs = append(errs, err) - } else { - nextAdUnit++ - } - } - return adUnits[:nextAdUnit], errs -} - -func initPBSAdUnit(imp *openrtb2.Imp, adUnit *pbs.PBSAdUnit) error { - var sizes []openrtb2.Format = nil - - video := pbs.PBSVideo{} - if imp.Video != nil { - video.Mimes = imp.Video.MIMEs - video.Minduration = imp.Video.MinDuration - video.Maxduration = imp.Video.MaxDuration - if imp.Video.StartDelay != nil { - video.Startdelay = int64(*imp.Video.StartDelay) - } - if imp.Video.Skip != nil { - video.Skippable = int(*imp.Video.Skip) - } - if len(imp.Video.PlaybackMethod) == 1 { - video.PlaybackMethod = int8(imp.Video.PlaybackMethod[0]) - } - if len(imp.Video.Protocols) > 0 { - video.Protocols = make([]int8, len(imp.Video.Protocols)) - for i := 0; i < len(imp.Video.Protocols); i++ { - video.Protocols[i] = int8(imp.Video.Protocols[i]) - } - } - // Fixes #360 - if imp.Video.W != 0 && imp.Video.H != 0 { - sizes = append(sizes, openrtb2.Format{ - W: imp.Video.W, - H: imp.Video.H, - }) - } - } - topFrame := int8(0) - if imp.Banner != nil { - topFrame = imp.Banner.TopFrame - sizes = append(sizes, imp.Banner.Format...) - } - - params, _, _, err := jsonparser.Get(imp.Ext, "bidder") - if err != nil { - return err - } - - mediaTypes := make([]pbs.MediaType, 0, 2) - if imp.Banner != nil { - mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_BANNER) - } - if imp.Video != nil { - mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_VIDEO) - } - if len(mediaTypes) == 0 { - return errors.New("legacy bidders can only bid on banner and video ad units") - } - - adUnit.Sizes = sizes - adUnit.TopFrame = topFrame - adUnit.Code = imp.ID - adUnit.BidID = imp.ID - adUnit.Params = json.RawMessage(params) - adUnit.Video = video - adUnit.MediaTypes = mediaTypes - adUnit.Instl = imp.Instl - - return nil -} - -// ---------------------------------------------------------------------------- -// Response transformations. - -// toNewResponse is a best-effort transformation of legacy Bids into an OpenRTB response. -func toNewResponse(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder, name openrtb_ext.BidderName) (*pbsOrtbSeatBid, []error) { - newBids, errs := transformBids(bids) - return &pbsOrtbSeatBid{ - bids: newBids, - httpCalls: transformDebugs(bidder.Debug), - }, errs -} - -func transformBids(legacyBids pbs.PBSBidSlice) ([]*pbsOrtbBid, []error) { - newBids := make([]*pbsOrtbBid, 0, len(legacyBids)) - var errs []error - for _, legacyBid := range legacyBids { - if legacyBid != nil { - newBid, err := transformBid(legacyBid) - if err == nil { - newBids = append(newBids, newBid) - } else { - errs = append(errs, err) - } - } - } - return newBids, errs -} - -func transformBid(legacyBid *pbs.PBSBid) (*pbsOrtbBid, error) { - newBid := transformBidToOrtb(legacyBid) - - newBidType, err := openrtb_ext.ParseBidType(legacyBid.CreativeMediaType) - if err != nil { - return nil, err - } - - return &pbsOrtbBid{ - bid: newBid, - bidType: newBidType, - }, nil -} - -func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb2.Bid { - return &openrtb2.Bid{ - ID: legacyBid.BidID, - ImpID: legacyBid.AdUnitCode, - CrID: legacyBid.Creative_id, - // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb2.Bid - // legacyBid.BidderCode is handled by the exchange, which already knows which bidder we are. - // legacyBid.BidHash is ignored, because it doesn't get sent in the response anyway - Price: legacyBid.Price, - NURL: legacyBid.NURL, - AdM: legacyBid.Adm, - W: legacyBid.Width, - H: legacyBid.Height, - DealID: legacyBid.DealId, - // TODO #216: Support CacheID here - // TODO: #216: Support CacheURL here - // ResponseTime is handled by the exchange, since it doesn't exist in the OpenRTB Bid - // AdServerTargeting is handled by the exchange. Rubicon's adapter is the only one which writes to it, - // but that doesn't matter since they're supporting OpenRTB directly. - } -} - -func transformDebugs(legacyDebugs []*pbs.BidderDebug) []*openrtb_ext.ExtHttpCall { - newDebug := make([]*openrtb_ext.ExtHttpCall, 0, len(legacyDebugs)) - for _, legacyDebug := range legacyDebugs { - if legacyDebug != nil { - newDebug = append(newDebug, transformDebug(legacyDebug)) - } - } - return newDebug -} - -func transformDebug(legacyDebug *pbs.BidderDebug) *openrtb_ext.ExtHttpCall { - return &openrtb_ext.ExtHttpCall{ - Uri: legacyDebug.RequestURI, - RequestBody: legacyDebug.RequestBody, - ResponseBody: legacyDebug.ResponseBody, - Status: legacyDebug.StatusCode, - } -} diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index fbb844525e7..f38a6c0266c 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -459,212 +459,3 @@ func TestSetTargeting(t *testing.T) { } } - -type TargetingTestData struct { - Description string - TargetData targetData - Auction auction - IsApp bool - CategoryMapping map[string]string - ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string -} - -var bid123 *openrtb.Bid = &openrtb.Bid{ - Price: 1.23, -} - -var bid111 *openrtb.Bid = &openrtb.Bid{ - Price: 1.11, - DealID: "mydeal", -} -var bid084 *openrtb.Bid = &openrtb.Bid{ - Price: 0.84, -} - -var TargetingTests []TargetingTestData = []TargetingTestData{ - { - Description: "Targeting winners only (most basic targeting example)", - TargetData: targetData{ - priceGranularity: openrtb_ext.PriceGranularityFromString("med"), - includeWinners: true, - }, - Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - bid: bid123, - bidType: openrtb_ext.BidTypeBanner, - }, - openrtb_ext.BidderRubicon: { - bid: bid084, - bidType: openrtb_ext.BidTypeBanner, - }, - }, - }, - }, - ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - "hb_bidder": "appnexus", - "hb_pb": "1.20", - }, - openrtb_ext.BidderRubicon: {}, - }, - }, - }, - { - Description: "Targeting on bidders only", - TargetData: targetData{ - priceGranularity: openrtb_ext.PriceGranularityFromString("med"), - includeBidderKeys: true, - }, - Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - bid: bid123, - bidType: openrtb_ext.BidTypeBanner, - }, - openrtb_ext.BidderRubicon: { - bid: bid084, - bidType: openrtb_ext.BidTypeBanner, - }, - }, - }, - }, - ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - "hb_bidder_appnexus": "appnexus", - "hb_pb_appnexus": "1.20", - }, - openrtb_ext.BidderRubicon: { - "hb_bidder_rubicon": "rubicon", - "hb_pb_rubicon": "0.80", - }, - }, - }, - }, - { - Description: "Full basic targeting with hd_format", - TargetData: targetData{ - priceGranularity: openrtb_ext.PriceGranularityFromString("med"), - includeWinners: true, - includeBidderKeys: true, - includeFormat: true, - }, - Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - bid: bid123, - bidType: openrtb_ext.BidTypeBanner, - }, - openrtb_ext.BidderRubicon: { - bid: bid084, - bidType: openrtb_ext.BidTypeBanner, - }, - }, - }, - }, - ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_pb": "1.20", - "hb_pb_appnexus": "1.20", - "hb_format": "banner", - "hb_format_appnexus": "banner", - }, - openrtb_ext.BidderRubicon: { - "hb_bidder_rubicon": "rubicon", - "hb_pb_rubicon": "0.80", - "hb_format_rubicon": "banner", - }, - }, - }, - }, - { - Description: "Cache and deal targeting test", - TargetData: targetData{ - priceGranularity: openrtb_ext.PriceGranularityFromString("med"), - includeBidderKeys: true, - cacheHost: "cache.prebid.com", - cachePath: "cache", - }, - Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - bid: bid123, - bidType: openrtb_ext.BidTypeBanner, - }, - openrtb_ext.BidderRubicon: { - bid: bid111, - bidType: openrtb_ext.BidTypeBanner, - }, - }, - }, - cacheIds: map[*openrtb.Bid]string{ - bid123: "55555", - bid111: "cacheme", - }, - }, - ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{ - "ImpId-1": { - openrtb_ext.BidderAppnexus: { - "hb_bidder_appnexus": "appnexus", - "hb_pb_appnexus": "1.20", - "hb_cache_id_appnexus": "55555", - "hb_cache_host_appnex": "cache.prebid.com", - "hb_cache_path_appnex": "cache", - }, - openrtb_ext.BidderRubicon: { - "hb_bidder_rubicon": "rubicon", - "hb_pb_rubicon": "1.10", - "hb_cache_id_rubicon": "cacheme", - "hb_deal_rubicon": "mydeal", - "hb_cache_host_rubico": "cache.prebid.com", - "hb_cache_path_rubico": "cache", - }, - }, - }, - }, -} - -func TestSetTargeting(t *testing.T) { - for _, test := range TargetingTests { - auc := &test.Auction - // Set rounded prices from the auction data - auc.setRoundedPrices(test.TargetData.priceGranularity) - winningBids := make(map[string]*pbsOrtbBid) - // Set winning bids from the auction data - for imp, bidsByBidder := range auc.winningBidsByBidder { - for _, bid := range bidsByBidder { - if winningBid, ok := winningBids[imp]; ok { - if winningBid.bid.Price < bid.bid.Price { - winningBids[imp] = bid - } - } else { - winningBids[imp] = bid - } - } - } - auc.winningBids = winningBids - targData := test.TargetData - targData.setTargeting(auc, test.IsApp, test.CategoryMapping) - for imp, targetsByBidder := range test.ExpectedBidTargetsByBidder { - for bidder, expected := range targetsByBidder { - assert.Equal(t, - expected, - auc.winningBidsByBidder[imp][bidder].bidTargets, - "Test: %s\nTargeting failed for bidder %s on imp %s.", - test.Description, - string(bidder), - imp) - } - } - } - -} diff --git a/gdpr/impl.go b/gdpr/impl.go index af3aa66b596..17a1a893e1c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -104,10 +104,6 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return SignalYes } -func (p *permissionsImpl) AMPException() bool { - return p.cfg.AMPException -} - func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) { if consent == "" { @@ -142,7 +138,11 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, // vendor will be nil if not a valid TCF2 consent string if vendor == nil { - return false, false, false, nil + if weakVendorEnforcement && parsedConsent.Version() == 2 { + vendor = vendorTrue{} + } else { + return false, false, false, nil + } } if !p.cfg.TCF2.Enabled { @@ -215,6 +215,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons if version != 2 { return } + vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) if err != nil { return @@ -249,25 +250,21 @@ func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidder openrt return true, true, true, nil } -func (a AlwaysAllow) AMPException() bool { - return false -} - -// Exporting to allow for easy test setups -type AlwaysFail struct{} +// vendorTrue claims everything. +type vendorTrue struct{} -func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) { - return false, nil +func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool { + return true } - -func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { - return false, nil +func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool { + return true } - -func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) { - return false, false, false, nil +func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool { + return true } - -func (a AlwaysFail) AMPException() bool { - return false +func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) { + return true } diff --git a/go.mod b/go.mod index b3002a7364e..ce9a441ce2d 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 - github.com/xorcare/pointer v1.1.0 + github.com/xorcare/pointer v1.1.0 // indirect github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index f0098ebf43f..318cd8f5211 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,12 @@ github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0= +github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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= diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 6c9344b325b..b2992afd831 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -286,9 +286,40 @@ func (me *MultiMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.Adapt } } +// RecordAdapterGDPRRequestBlocked across all engines +func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { + for _, thisME := range *me { + thisME.RecordAdapterGDPRRequestBlocked(adapter) + } +} + // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) type DummyMetricsEngine struct{} +func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { + panic("implement me") +} + +func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() { + panic("implement me") +} + +func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) { + panic("implement me") +} + +func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { + panic("implement me") +} + +func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { + panic("implement me") +} + +func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { + panic("implement me") +} + // RecordRequest as a noop func (me *DummyMetricsEngine) RecordRequest(labels metrics.Labels) { } @@ -393,26 +424,6 @@ func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } -// RecordAdapterDuplicateBidID as a noop -func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { -} - -// RecordRequestHavingDuplicateBidID as a noop -func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() { -} - -// RecordPodImpGenTime as a noop -func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, start time.Time) { -} - -// RecordPodCombGenTime as a noop -func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { -} - -// RecordPodCompititveExclusionTime as a noop -func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { -} - -// RecordAdapterVideoBidDuration as a noop -func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { +// RecordAdapterGDPRRequestBlocked as a noop +func (me *DummyMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { } diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 2529eaf4765..45dece19f7d 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -88,19 +88,20 @@ type Metrics struct { // AdapterMetrics houses the metrics for a particular adapter type AdapterMetrics struct { - NoCookieMeter metrics.Meter - ErrorMeters map[AdapterError]metrics.Meter - NoBidMeter metrics.Meter - GotBidsMeter metrics.Meter - RequestTimer metrics.Timer - PriceHistogram metrics.Histogram - BidsReceivedMeter metrics.Meter - PanicMeter metrics.Meter - MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics - ConnCreated metrics.Counter - ConnReused metrics.Counter - ConnWaitTime metrics.Timer - TLSHandshakeTimer metrics.Timer + NoCookieMeter metrics.Meter + ErrorMeters map[AdapterError]metrics.Meter + NoBidMeter metrics.Meter + GotBidsMeter metrics.Meter + RequestTimer metrics.Timer + PriceHistogram metrics.Histogram + BidsReceivedMeter metrics.Meter + PanicMeter metrics.Meter + MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics + ConnCreated metrics.Counter + ConnReused metrics.Counter + ConnWaitTime metrics.Timer + GDPRRequestBlocked metrics.Meter + TLSHandshakeTimer metrics.Timer } type MarkupDeliveryMetrics struct { @@ -321,6 +322,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet newAdapter.ConnWaitTime = &metrics.NilTimer{} newAdapter.TLSHandshakeTimer = &metrics.NilTimer{} } + if !disabledMetrics.AdapterGDPRRequestBlocked { + newAdapter.GDPRRequestBlocked = blankMeter + } for _, err := range AdapterErrors() { newAdapter.ErrorMeters[err] = blankMeter } @@ -366,6 +370,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry) } am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry) + am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry) } func makeDeliveryMetrics(registry metrics.Registry, prefix string, bidType openrtb_ext.BidType) *MarkupDeliveryMetrics { @@ -730,6 +735,20 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { return } +func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + if me.MetricsDisabled.AdapterGDPRRequestBlocked { + return + } + + am, ok := me.AdapterMetrics[adapterName] + if !ok { + glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", string(adapterName)) + return + } + + am.GDPRRequestBlocked.Mark(1) +} + // RecordAdapterDuplicateBidID as noop func (me *Metrics) RecordAdapterDuplicateBidID(adaptor string, collisions int) { } diff --git a/metrics/metrics.go b/metrics/metrics.go index cf86f1f5556..7632ab1812d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -363,6 +363,7 @@ type MetricsEngine interface { RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) RecordTimeoutNotice(sucess bool) RecordRequestPrivacy(privacy PrivacyLabels) + RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) // RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor // gives the bid response with multiple bids containing same bid.ID diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index b211b2faa22..8cc91b31c8a 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -141,6 +141,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) { me.Called(privacy) } +// RecordAdapterGDPRRequestBlocked mock +func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + me.Called(adapterName) +} + // RecordAdapterDuplicateBidID mock func (me *MetricsEngineMock) RecordAdapterDuplicateBidID(adaptor string, collisions int) { me.Called(adaptor, collisions) @@ -169,4 +174,4 @@ func (me *MetricsEngineMock) RecordPodCompititveExclusionTime(labels PodLabels, //RecordAdapterVideoBidDuration mock func (me *MetricsEngineMock) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) { me.Called(labels, videoBidDuration) -} +} \ No newline at end of file diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index a0a13455493..1a854621372 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -41,7 +41,7 @@ type Metrics struct { storedVideoErrors *prometheus.CounterVec timeoutNotifications *prometheus.CounterVec dnsLookupTimer prometheus.Histogram - //tlsHandhakeTimer prometheus.Histogram + //tlsHandhakeTimer prometheus.Histogram privacyCCPA *prometheus.CounterVec privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec @@ -63,6 +63,7 @@ type Metrics struct { adapterDuplicateBidIDCounter *prometheus.CounterVec adapterVideoBidDuration *prometheus.HistogramVec tlsHandhakeTimer *prometheus.HistogramVec + adapterGDPRBlockedRequests *prometheus.CounterVec // Account Metrics accountRequests *prometheus.CounterVec @@ -309,6 +310,13 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of total requests to Prebid Server where the LMT flag was set by source", []string{sourceLabel}) + if !metrics.metricsDisabled.AdapterGDPRRequestBlocked { + metrics.adapterGDPRBlockedRequests = newCounter(cfg, metrics.Registry, + "adapter_gdpr_requests_blocked", + "Count of total bidder requests blocked due to unsatisfied GDPR purpose 2 legal basis", + []string{adapterLabel}) + } + metrics.adapterBids = newCounter(cfg, metrics.Registry, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -764,6 +772,16 @@ func (m *Metrics) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } } +func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + if m.metricsDisabled.AdapterGDPRRequestBlocked { + return + } + + m.adapterGDPRBlockedRequests.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Inc() +} + // RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor // gives the bid response with multiple bids containing same bid.ID // ensure collisions value is greater than 1. This function will not give any error diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 79e4b260b96..cf4db871dea 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1364,18 +1364,21 @@ func TestRecordAdapterConnections(t *testing.T) { } } -func TestDisableAdapterConnections(t *testing.T) { +func TestDisabledMetrics(t *testing.T) { prometheusMetrics := NewMetrics(config.PrometheusMetrics{ Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{AdapterConnectionMetrics: true}) + }, config.DisabledMetrics{ + AdapterConnectionMetrics: true, + AdapterGDPRRequestBlocked: true, + }) // Assert counter vector was not initialized assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") - assert.Nil(t, prometheusMetrics.tlsHandhakeTimer, "Counter Vector tlsHandhakeTimer should be nil") + assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil") } func TestRecordRequestPrivacy(t *testing.T) { @@ -1700,3 +1703,18 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count") assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") } + +func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { + m := createMetricsForTesting() + + m.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + + assertCounterVecValue(t, + "Increment adapter GDPR request blocked counter", + "adapter_gdpr_requests_blocked", + m.adapterGDPRBlockedRequests, + 1, + prometheus.Labels{ + adapterLabel: string(openrtb_ext.BidderAppnexus), + }) +} diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 83022d41dd7..0e5c80636db 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -112,39 +112,3 @@ func tcfVersionsAsString() []string { } return valuesAsString } - -func storedDataTypesAsString() []string { - values := pbsmetrics.StoredDataTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataFetchTypesAsString() []string { - values := pbsmetrics.StoredDataFetchTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataErrorsAsString() []string { - values := pbsmetrics.StoredDataErrors() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func tcfVersionsAsString() []string { - values := pbsmetrics.TCFVersions() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 44d2a02bcf0..d120a1283e7 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -73,116 +73,127 @@ func IsBidderNameReserved(name string) bool { // // Please keep this list alphabetized to minimize merge conflicts. const ( - Bidder33Across BidderName = "33across" - BidderAcuityAds BidderName = "acuityads" - BidderAdf BidderName = "adf" - BidderAdform BidderName = "adform" - BidderAdgeneration BidderName = "adgeneration" - BidderAdhese BidderName = "adhese" - BidderAdkernel BidderName = "adkernel" - BidderAdkernelAdn BidderName = "adkernelAdn" - BidderAdman BidderName = "adman" - BidderAdmixer BidderName = "admixer" - BidderAdOcean BidderName = "adocean" - BidderAdoppler BidderName = "adoppler" - BidderAdot BidderName = "adot" - BidderAdpone BidderName = "adpone" - BidderAdprime BidderName = "adprime" - BidderAdtarget BidderName = "adtarget" - BidderAdtelligent BidderName = "adtelligent" - BidderAdvangelists BidderName = "advangelists" - BidderAdxcg BidderName = "adxcg" - BidderAdyoulike BidderName = "adyoulike" - BidderAJA BidderName = "aja" - BidderAMX BidderName = "amx" - BidderApplogy BidderName = "applogy" - BidderAppnexus BidderName = "appnexus" - BidderAudienceNetwork BidderName = "audienceNetwork" - BidderAvocet BidderName = "avocet" - BidderBeachfront BidderName = "beachfront" - BidderBeintoo BidderName = "beintoo" - BidderBetween BidderName = "between" - BidderBidmachine BidderName = "bidmachine" - BidderBrightroll BidderName = "brightroll" - BidderColossus BidderName = "colossus" - BidderConnectAd BidderName = "connectad" - BidderConsumable BidderName = "consumable" - BidderConversant BidderName = "conversant" - BidderCpmstar BidderName = "cpmstar" - BidderCriteo BidderName = "criteo" - BidderDatablocks BidderName = "datablocks" - BidderDmx BidderName = "dmx" - BidderDecenterAds BidderName = "decenterads" - BidderDeepintent BidderName = "deepintent" - BidderEmxDigital BidderName = "emx_digital" - BidderEngageBDR BidderName = "engagebdr" - BidderEPlanning BidderName = "eplanning" - BidderEpom BidderName = "epom" - BidderGamma BidderName = "gamma" - BidderGamoshi BidderName = "gamoshi" - BidderGrid BidderName = "grid" - BidderGumGum BidderName = "gumgum" - BidderImprovedigital BidderName = "improvedigital" - BidderInMobi BidderName = "inmobi" - BidderInvibes BidderName = "invibes" - BidderIx BidderName = "ix" - BidderJixie BidderName = "jixie" - BidderKidoz BidderName = "kidoz" - BidderKrushmedia BidderName = "krushmedia" - BidderKubient BidderName = "kubient" - BidderLifestreet BidderName = "lifestreet" - BidderLockerDome BidderName = "lockerdome" - BidderLogicad BidderName = "logicad" - BidderLunaMedia BidderName = "lunamedia" - BidderMarsmedia BidderName = "marsmedia" - BidderMediafuse BidderName = "mediafuse" - BidderMgid BidderName = "mgid" - BidderMobfoxpb BidderName = "mobfoxpb" - BidderMobileFuse BidderName = "mobilefuse" - BidderNanoInteractive BidderName = "nanointeractive" - BidderNinthDecimal BidderName = "ninthdecimal" - BidderNoBid BidderName = "nobid" - BidderOneTag BidderName = "onetag" - BidderOpenx BidderName = "openx" - BidderOrbidder BidderName = "orbidder" - BidderOutbrain BidderName = "outbrain" - BidderPangle BidderName = "pangle" - BidderPubmatic BidderName = "pubmatic" - BidderPubnative BidderName = "pubnative" - BidderPulsepoint BidderName = "pulsepoint" - BidderRevcontent BidderName = "revcontent" - BidderRhythmone BidderName = "rhythmone" - BidderRTBHouse BidderName = "rtbhouse" - BidderRubicon BidderName = "rubicon" - BidderSharethrough BidderName = "sharethrough" - BidderSilverMob BidderName = "silvermob" - BidderSmaato BidderName = "smaato" - BidderSmartAdserver BidderName = "smartadserver" - BidderSmartRTB BidderName = "smartrtb" - BidderSmartyAds BidderName = "smartyads" - BidderSomoaudience BidderName = "somoaudience" - BidderSonobi BidderName = "sonobi" - BidderSovrn BidderName = "sovrn" - BidderSpotX BidderName = "spotx" - BidderSynacormedia BidderName = "synacormedia" - BidderTappx BidderName = "tappx" - BidderTelaria BidderName = "telaria" - BidderTriplelift BidderName = "triplelift" - BidderTripleliftNative BidderName = "triplelift_native" - BidderTrustX BidderName = "trustx" - BidderUcfunnel BidderName = "ucfunnel" - BidderUnicorn BidderName = "unicorn" - BidderUnruly BidderName = "unruly" - BidderValueImpression BidderName = "valueimpression" - BidderVASTBidder BidderName = "vastbidder" - BidderVerizonMedia BidderName = "verizonmedia" - BidderVisx BidderName = "visx" - BidderVrtcal BidderName = "vrtcal" - BidderYeahmobi BidderName = "yeahmobi" - BidderYieldlab BidderName = "yieldlab" - BidderYieldmo BidderName = "yieldmo" - BidderYieldone BidderName = "yieldone" - BidderZeroClickFraud BidderName = "zeroclickfraud" + Bidder33Across BidderName = "33across" + BidderAcuityAds BidderName = "acuityads" + BidderAdf BidderName = "adf" + BidderAdform BidderName = "adform" + BidderAdgeneration BidderName = "adgeneration" + BidderAdhese BidderName = "adhese" + BidderAdkernel BidderName = "adkernel" + BidderAdkernelAdn BidderName = "adkernelAdn" + BidderAdman BidderName = "adman" + BidderAdmixer BidderName = "admixer" + BidderAdOcean BidderName = "adocean" + BidderAdoppler BidderName = "adoppler" + BidderAdot BidderName = "adot" + BidderAdpone BidderName = "adpone" + BidderAdprime BidderName = "adprime" + BidderAdtarget BidderName = "adtarget" + BidderAdtelligent BidderName = "adtelligent" + BidderAdvangelists BidderName = "advangelists" + BidderAdxcg BidderName = "adxcg" + BidderAdyoulike BidderName = "adyoulike" + BidderAJA BidderName = "aja" + BidderAlgorix BidderName = "algorix" + BidderAMX BidderName = "amx" + BidderApplogy BidderName = "applogy" + BidderAppnexus BidderName = "appnexus" + BidderAudienceNetwork BidderName = "audienceNetwork" + BidderAvocet BidderName = "avocet" + BidderAxonix BidderName = "axonix" + BidderBeachfront BidderName = "beachfront" + BidderBeintoo BidderName = "beintoo" + BidderBetween BidderName = "between" + BidderBidmachine BidderName = "bidmachine" + BidderBidmyadz BidderName = "bidmyadz" + BidderBidsCube BidderName = "bidscube" + BidderBmtm BidderName = "bmtm" + BidderBrightroll BidderName = "brightroll" + BidderColossus BidderName = "colossus" + BidderConnectAd BidderName = "connectad" + BidderConsumable BidderName = "consumable" + BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" + BidderCriteo BidderName = "criteo" + BidderDatablocks BidderName = "datablocks" + BidderDmx BidderName = "dmx" + BidderDecenterAds BidderName = "decenterads" + BidderDeepintent BidderName = "deepintent" + BidderEmxDigital BidderName = "emx_digital" + BidderEngageBDR BidderName = "engagebdr" + BidderEPlanning BidderName = "eplanning" + BidderEpom BidderName = "epom" + BidderEVolution BidderName = "e_volution" + BidderGamma BidderName = "gamma" + BidderGamoshi BidderName = "gamoshi" + BidderGrid BidderName = "grid" + BidderGumGum BidderName = "gumgum" + BidderImprovedigital BidderName = "improvedigital" + BidderInMobi BidderName = "inmobi" + BidderInteractiveoffers BidderName = "interactiveoffers" + BidderInvibes BidderName = "invibes" + BidderIx BidderName = "ix" + BidderJixie BidderName = "jixie" + BidderKayzen BidderName = "kayzen" + BidderKidoz BidderName = "kidoz" + BidderKrushmedia BidderName = "krushmedia" + BidderKubient BidderName = "kubient" + BidderLockerDome BidderName = "lockerdome" + BidderLogicad BidderName = "logicad" + BidderLunaMedia BidderName = "lunamedia" + BidderSaLunaMedia BidderName = "sa_lunamedia" + BidderMadvertise BidderName = "madvertise" + BidderMarsmedia BidderName = "marsmedia" + BidderMediafuse BidderName = "mediafuse" + BidderMgid BidderName = "mgid" + BidderMobfoxpb BidderName = "mobfoxpb" + BidderMobileFuse BidderName = "mobilefuse" + BidderNanoInteractive BidderName = "nanointeractive" + BidderNinthDecimal BidderName = "ninthdecimal" + BidderNoBid BidderName = "nobid" + BidderOneTag BidderName = "onetag" + BidderOpenx BidderName = "openx" + BidderOrbidder BidderName = "orbidder" + BidderOutbrain BidderName = "outbrain" + BidderPangle BidderName = "pangle" + BidderPubmatic BidderName = "pubmatic" + BidderPubnative BidderName = "pubnative" + BidderPulsepoint BidderName = "pulsepoint" + BidderRevcontent BidderName = "revcontent" + BidderRhythmone BidderName = "rhythmone" + BidderRTBHouse BidderName = "rtbhouse" + BidderRubicon BidderName = "rubicon" + BidderSharethrough BidderName = "sharethrough" + BidderSilverMob BidderName = "silvermob" + BidderSmaato BidderName = "smaato" + BidderSmartAdserver BidderName = "smartadserver" + BidderSmartRTB BidderName = "smartrtb" + BidderSmartyAds BidderName = "smartyads" + BidderSmileWanted BidderName = "smilewanted" + BidderSomoaudience BidderName = "somoaudience" + BidderSonobi BidderName = "sonobi" + BidderSovrn BidderName = "sovrn" + BidderSpotX BidderName = "spotx" + BidderSynacormedia BidderName = "synacormedia" + BidderTappx BidderName = "tappx" + BidderTelaria BidderName = "telaria" + BidderTriplelift BidderName = "triplelift" + BidderTripleliftNative BidderName = "triplelift_native" + BidderTrustX BidderName = "trustx" + BidderUcfunnel BidderName = "ucfunnel" + BidderUnicorn BidderName = "unicorn" + BidderUnruly BidderName = "unruly" + BidderValueImpression BidderName = "valueimpression" + BidderVASTBidder BidderName = "vastbidder" + BidderVerizonMedia BidderName = "verizonmedia" + BidderVisx BidderName = "visx" + BidderViewdeos BidderName = "viewdeos" + BidderVrtcal BidderName = "vrtcal" + BidderYeahmobi BidderName = "yeahmobi" + BidderYieldlab BidderName = "yieldlab" + BidderYieldmo BidderName = "yieldmo" + BidderYieldone BidderName = "yieldone" + BidderZeroClickFraud BidderName = "zeroclickfraud" ) // CoreBidderNames returns a slice of all core bidders. @@ -209,15 +220,20 @@ func CoreBidderNames() []BidderName { BidderAdxcg, BidderAdyoulike, BidderAJA, + BidderAlgorix, BidderAMX, BidderApplogy, BidderAppnexus, BidderAudienceNetwork, BidderAvocet, + BidderAxonix, BidderBeachfront, BidderBeintoo, BidderBetween, BidderBidmachine, + BidderBidmyadz, + BidderBidsCube, + BidderBmtm, BidderBrightroll, BidderColossus, BidderConnectAd, @@ -233,22 +249,26 @@ func CoreBidderNames() []BidderName { BidderEngageBDR, BidderEPlanning, BidderEpom, + BidderEVolution, BidderGamma, BidderGamoshi, BidderGrid, BidderGumGum, BidderImprovedigital, BidderInMobi, + BidderInteractiveoffers, BidderInvibes, BidderIx, BidderJixie, + BidderKayzen, BidderKidoz, BidderKrushmedia, BidderKubient, - BidderLifestreet, BidderLockerDome, BidderLogicad, BidderLunaMedia, + BidderSaLunaMedia, + BidderMadvertise, BidderMarsmedia, BidderMediafuse, BidderMgid, @@ -275,6 +295,7 @@ func CoreBidderNames() []BidderName { BidderSmartAdserver, BidderSmartRTB, BidderSmartyAds, + BidderSmileWanted, BidderSomoaudience, BidderSonobi, BidderSovrn, @@ -291,6 +312,7 @@ func CoreBidderNames() []BidderName { BidderValueImpression, BidderVASTBidder, BidderVerizonMedia, + BidderViewdeos, BidderVisx, BidderVrtcal, BidderYeahmobi, diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index d4917bd2ce1..26ebf7dc74b 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -118,64 +118,3 @@ func TestIsBidderNameReserved(t *testing.T) { assert.Equal(t, test.expected, result, test.bidder) } } - -func TestBidderListDoesNotDefineContext(t *testing.T) { - bidders := BidderList() - assert.NotContains(t, bidders, BidderNameContext) -} - -// TestBidderUniquenessGatekeeping acts as a gatekeeper of bidder name uniqueness. If this test fails -// when you're building a new adapter, please consider choosing a different bidder name to maintain the -// current uniqueness threshold, or else start a discussion in the PR. -func TestBidderUniquenessGatekeeping(t *testing.T) { - // Get List Of Bidders - // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. - var bidders []string - for _, bidder := range BidderMap { - if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderSmartadserver { - bidders = append(bidders, string(bidder)) - } - } - - currentThreshold := 6 - measuredThreshold := minUniquePrefixLength(bidders) - - assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") - assert.LessOrEqual(t, measuredThreshold, currentThreshold) -} - -// minUniquePrefixLength measures the minimun amount of characters needed to uniquely identify -// one of the strings, or returns 0 if there are duplicates. -func minUniquePrefixLength(b []string) int { - targetingKeyMaxLength := 20 - for prefixLength := 1; prefixLength <= targetingKeyMaxLength; prefixLength++ { - if uniqueForPrefixLength(b, prefixLength) { - return prefixLength - } - } - return 0 -} - -func uniqueForPrefixLength(b []string, prefixLength int) bool { - m := make(map[string]struct{}) - - if prefixLength <= 0 { - return false - } - - for i, n := range b { - ns := string(n) - - if len(ns) > prefixLength { - ns = ns[0:prefixLength] - } - - m[ns] = struct{}{} - - if len(m) != i+1 { - return false - } - } - - return true -} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index b4008a93434..24766ab1603 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -46,6 +46,13 @@ type ExtRequestPrebid struct { // Macros specifies list of custom macros along with the values. This is used while forming // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding Macros map[string]string `json:"macros,omitempty"` + + CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` +} + +type ExtRequestCurrency struct { + ConversionRates map[string]map[string]float64 `json:"rates"` + UsePBSRates *bool `json:"usepbsrates"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index d57791e0d64..9274c5b58be 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -28,33 +28,4 @@ func TestValidateConsent(t *testing.T) { result := ValidateConsent(test.consent) assert.Equal(t, test.expected, result, test.description) } -} - -func TestValidateConsent(t *testing.T) { - testCases := []struct { - description string - consent string - expectError bool - }{ - { - description: "Invalid", - consent: "", - expectError: true, - }, - { - description: "Valid", - consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expectError: false, - }, - } - - for _, test := range testCases { - result := ValidateConsent(test.consent) - - if test.expectError { - assert.Error(t, result, test.description) - } else { - assert.NoError(t, result, test.description) - } - } -} +} \ No newline at end of file diff --git a/privacy/policies.go b/privacy/policies.go index 71f4a8adbb4..bc844a4e463 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -12,28 +12,3 @@ type Policies struct { GDPR gdpr.Policy LMT lmt.Policy } - -// ReadPoliciesFromConsent inspects the consent string kind and sets the corresponding values in a new Policies object. -func ReadPoliciesFromConsent(consent string) (Policies, bool) { - if len(consent) == 0 { - return Policies{}, false - } - - if err := gdpr.ValidateConsent(consent); err == nil { - return Policies{ - GDPR: gdpr.Policy{ - Consent: consent, - }, - }, true - } - - if err := ccpa.ValidateConsent(consent); err == nil { - return Policies{ - CCPA: ccpa.Policy{ - Value: consent, - }, - }, true - } - - return Policies{}, false -} diff --git a/router/router.go b/router/router.go index 400b71a4318..df4d68b3b12 100644 --- a/router/router.go +++ b/router/router.go @@ -7,7 +7,6 @@ import ( "database/sql" "encoding/json" "fmt" - "github.com/prebid/prebid-server/endpoints/events" "io/ioutil" "net" "net/http" diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index b71d481df93..d4dc0d8bf0a 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -58,6 +58,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderGrid): syncConfig, string(openrtb_ext.BidderGumGum): syncConfig, string(openrtb_ext.BidderImprovedigital): syncConfig, + string(openrtb_ext.BidderInMobi): syncConfig, string(openrtb_ext.BidderInvibes): syncConfig, string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderJixie): syncConfig, @@ -109,30 +110,35 @@ func TestNewSyncerMap(t *testing.T) { } adaptersWithoutSyncers := map[openrtb_ext.BidderName]bool{ - openrtb_ext.BidderAdgeneration: true, - openrtb_ext.BidderAdhese: true, - openrtb_ext.BidderAdoppler: true, - openrtb_ext.BidderAdot: true, - openrtb_ext.BidderAdprime: true, - openrtb_ext.BidderApplogy: true, - openrtb_ext.BidderBidmachine: true, - openrtb_ext.BidderEpom: true, - openrtb_ext.BidderDecenterAds: true, - openrtb_ext.BidderInMobi: true, - openrtb_ext.BidderKidoz: true, - openrtb_ext.BidderKubient: true, - openrtb_ext.BidderMobfoxpb: true, - openrtb_ext.BidderMobileFuse: true, - openrtb_ext.BidderOrbidder: true, - openrtb_ext.BidderPangle: true, - openrtb_ext.BidderPubnative: true, - openrtb_ext.BidderRevcontent: true, - openrtb_ext.BidderSilverMob: true, - openrtb_ext.BidderSmaato: true, - openrtb_ext.BidderSpotX: true, - openrtb_ext.BidderVASTBidder: true, - openrtb_ext.BidderUnicorn: true, - openrtb_ext.BidderYeahmobi: true, + openrtb_ext.BidderAdgeneration: true, + openrtb_ext.BidderAdhese: true, + openrtb_ext.BidderAdoppler: true, + openrtb_ext.BidderAdot: true, + openrtb_ext.BidderAdprime: true, + openrtb_ext.BidderAlgorix: true, + openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderAxonix: true, + openrtb_ext.BidderBidmachine: true, + openrtb_ext.BidderBidsCube: true, + openrtb_ext.BidderEpom: true, + openrtb_ext.BidderDecenterAds: true, + openrtb_ext.BidderInteractiveoffers: true, + openrtb_ext.BidderKayzen: true, + openrtb_ext.BidderKidoz: true, + openrtb_ext.BidderKubient: true, + openrtb_ext.BidderMadvertise: true, + openrtb_ext.BidderMobfoxpb: true, + openrtb_ext.BidderMobileFuse: true, + openrtb_ext.BidderOrbidder: true, + openrtb_ext.BidderPangle: true, + openrtb_ext.BidderPubnative: true, + openrtb_ext.BidderRevcontent: true, + openrtb_ext.BidderSilverMob: true, + openrtb_ext.BidderSmaato: true, + openrtb_ext.BidderSpotX: true, + openrtb_ext.BidderUnicorn: true, + openrtb_ext.BidderVASTBidder: true, + openrtb_ext.BidderYeahmobi: true, } for bidder, config := range cfg.Adapters { From 1a3adb0f1ba4861e84c999f1f098cb1edf501f36 Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Tue, 3 Aug 2021 21:58:38 +0530 Subject: [PATCH 600/603] Reverted some changes after prebid-server upgrade --- exchange/exchange.go | 14 +++++++-- exchange/exchange_test.go | 44 +++++++++++++++++++++++++++ metrics/config/metrics.go | 6 ---- metrics/prometheus/prometheus_test.go | 1 + openrtb_ext/bid.go | 4 ++- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index a944eabadb2..fa8b51901ff 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -543,9 +543,19 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest, allBidders = sb.String()[:sb.Len()-1] } + bidderRequestStr := "" + if nil != bidderRequest.BidRequest { + value, err := json.Marshal(bidderRequest.BidRequest) + if nil == err { + bidderRequestStr = string(value) + } else { + bidderRequestStr = err.Error() + } + } + glog.Errorf("OpenRTB auction recovered panic from Bidder %s: %v. "+ - "Account id: %s, All Bidders: %s, Stack trace is: %v", - bidderRequest.BidderCoreName, r, bidderRequest.BidderLabels.PubID, allBidders, string(debug.Stack())) + "Account id: %s, All Bidders: %s, BidRequest: %s, Stack trace is: %v", + bidderRequest.BidderCoreName, r, bidderRequest.BidderLabels.PubID, allBidders, bidderRequestStr, string(debug.Stack())) e.me.RecordAdapterPanic(bidderRequest.BidderLabels) // Let the master request know that there is no data here brw := new(bidResponseWrapper) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 9932e865b8b..6f49b754bb9 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -4318,3 +4318,47 @@ func newTestRtbAdapter(name string) *bidderAdapter { BidderName: openrtb_ext.BidderName(name), } } + +func TestRecordAdaptorDuplicateBidIDs(t *testing.T) { + type bidderCollisions = map[string]int + testCases := []struct { + scenario string + bidderCollisions *bidderCollisions // represents no of collisions detected for bid.id at bidder level for given request + hasCollision bool + }{ + {scenario: "invalid collision value", bidderCollisions: &map[string]int{"bidder-1": -1}, hasCollision: false}, + {scenario: "no collision", bidderCollisions: &map[string]int{"bidder-1": 0}, hasCollision: false}, + {scenario: "one collision", bidderCollisions: &map[string]int{"bidder-1": 1}, hasCollision: false}, + {scenario: "multiple collisions", bidderCollisions: &map[string]int{"bidder-1": 2}, hasCollision: true}, // when 2 collisions it counter will be 1 + {scenario: "multiple bidders", bidderCollisions: &map[string]int{"bidder-1": 2, "bidder-2": 4}, hasCollision: true}, + {scenario: "multiple bidders with bidder-1 no collision", bidderCollisions: &map[string]int{"bidder-1": 1, "bidder-2": 4}, hasCollision: true}, + {scenario: "no bidders", bidderCollisions: nil, hasCollision: false}, + } + testEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, nil) + + for _, testcase := range testCases { + var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid + if nil == testcase.bidderCollisions { + break + } + adapterBids = make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + for bidder, collisions := range *testcase.bidderCollisions { + bids := make([]*pbsOrtbBid, 0) + testBidID := "bid_id_for_bidder_" + bidder + // add bids as per collisions value + bidCount := 0 + for ; bidCount < collisions; bidCount++ { + bids = append(bids, &pbsOrtbBid{ + bid: &openrtb2.Bid{ + ID: testBidID, + }, + }) + } + if nil == adapterBids[openrtb_ext.BidderName(bidder)] { + adapterBids[openrtb_ext.BidderName(bidder)] = new(pbsOrtbSeatBid) + } + adapterBids[openrtb_ext.BidderName(bidder)].bids = bids + } + assert.Equal(t, testcase.hasCollision, recordAdaptorDuplicateBidIDs(testEngine, adapterBids)) + } +} diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 799ce081c58..e9a436eed49 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -297,27 +297,21 @@ func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ex type DummyMetricsEngine struct{} func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { - panic("implement me") } func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() { - panic("implement me") } func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) { - panic("implement me") } func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { - panic("implement me") } func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { - panic("implement me") } func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { - panic("implement me") } // RecordRequest as a noop diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index cf4db871dea..b2e383140ce 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1378,6 +1378,7 @@ func TestDisabledMetrics(t *testing.T) { assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") + assert.Nil(t, prometheusMetrics.tlsHandhakeTimer, "Counter Vector tlsHandhakeTimer should be nil") assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil") } diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index e3dd79e2e67..f3cfb3df6a4 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -1,12 +1,14 @@ package openrtb_ext import ( + "encoding/json" "fmt" ) // ExtBid defines the contract for bidresponse.seatbid.bid[i].ext type ExtBid struct { - Prebid *ExtBidPrebid `json:"prebid,omitempty"` + Prebid *ExtBidPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder,omitempty"` } // ExtBidPrebid defines the contract for bidresponse.seatbid.bid[i].ext.prebid From a285aec93272ee4468e833dc4360836b0c2a445f Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Wed, 4 Aug 2021 12:32:24 +0530 Subject: [PATCH 601/603] Fixed ctv_auction.go after merging prebid-0.170.0 --- endpoints/openrtb2/ctv_auction.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index a2299517695..00a2b254b32 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -104,6 +104,7 @@ func NewCTVEndpoint( func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { defer util.TimeTrack(time.Now(), "CTVAuctionEndpoint") + var reqWrapper *openrtb_ext.RequestWrapper var request *openrtb2.BidRequest var response *openrtb2.BidResponse var err error @@ -136,10 +137,11 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R }() //Parse ORTB Request and do Standard Validation - request, errL = deps.parseRequest(r) + reqWrapper, errL = deps.parseRequest(r) if errortypes.ContainsFatalError(errL) && writeError(errL, w, &deps.labels) { return } + request = reqWrapper.BidRequest util.JLogf("Original BidRequest", request) //TODO: REMOVE LOG From dc9dd509a4ccc2fe5a06b2573e4faefc788a578f Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Fri, 13 Aug 2021 13:48:43 +0530 Subject: [PATCH 602/603] Added missing gdpr.default_value --- config/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.go b/config/config.go index 3ad69f0f419..ee659418f44 100644 --- a/config/config.go +++ b/config/config.go @@ -1008,6 +1008,7 @@ func SetupViper(v *viper.Viper, filename string) { v.BindEnv("gdpr.default_value") v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) + v.SetDefault("gdpr.default_value", "0") v.SetDefault("gdpr.usersync_if_ambiguous", true) v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) From b4416bbdd177a72b84015112f96945046781b13d Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Tue, 17 Aug 2021 21:14:01 +0530 Subject: [PATCH 603/603] Updated usersync url for bidder Unruly --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index ee659418f44..efb71adcc31 100644 --- a/config/config.go +++ b/config/config.go @@ -693,7 +693,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") // openrtb_ext.BidderUnicorn doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://sync.1rx.io/usersync2/rmpssp?sub=openwrap&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderVASTBidder doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")